React 组件介绍
组件允许你将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。
组件表示页面中的部分功能,组合多个组件实现完整的页面。
功能特点:可复用、独立、可组合。
React组件的两种创建方式
React 创建组件方法:
- 使用函数
function
- 使用类
class
函数组件
1)什么是函数组件?
2)定义函数组件
- 语法约定
- 函数名称
首字母必需大写
,React 据此来区分组件和 HTML 元素。 - 函数
必须有返回值
,表示该组件的 UI 结构,如果不渲染任何内容可返回null
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| function Header() { return <div>头部组件</div>; }
const Footer = () => { return <div>底部组件</div>; };
const Loading = () => { const loading = false; return loading ? <div>正在加载...</div> : null; };
|
3)使用组件
- 函数的名称就是组件名称,使用组件就是把组件名称当标签使用即可。
- 组件标签可以是单标签也可以是双标签。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import ReactDom from 'react-dom';
function Header() { return <div>头部组件</div>; }
const Footer = () => { return <div>底部组件</div>; };
const Loading = () => { const loading = false; return loading ? <div>正在加载...</div> : null; };
const App = () => { return ( <> <Header /> <Loading /> <Footer /> </> ); };
ReactDom.render(<App />, document.getElementById('root'));
|
总结
- 创建函数组件,首字母大写,需要返回值,不渲染就返回
null
。 - 使用函数组件,组件名称当作标签使用即可。
类组件
class 语法
复习一下定义class、定义属性、定义函数。
1 2 3 4 5 6 7
| class Animal { address = '地球'; eat() { console.log('吃'); } }
|
extends
继承父类
1 2 3 4 5 6 7 8 9 10 11
| class Cat extends Animal { run() { console.log('跑'); } } const cat = new Cat(); cat.run(); cat.eat(); console.log(cat.address);
|
总结: class
创建类,extends
继承类,可以使用父类的属性和函数。
1)什么是类组件?
2)定义类组件
- 约定:类名首字母必需大写
- 约定:必须继承
React.Component
父类 - 约定:必需有
render
函数,返回 UI 结构,无渲染可返回 null
1 2 3 4 5 6 7
| import { Component } from 'react'; class Header extends Component { render() { return <div>头部组件</div>; } }
|
3)使用类组件
- 类名称就是组件名称,使用组件就是把组件名称当标签使用即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Component } from 'react'; import ReactDom from 'react-dom';
class Header extends Component { render() { return <div>头部组件</div>; } }
class App extends Component { render() { return ( <> <Header /> </> ); } } ReactDom.render(<App />, document.getElementById('root'));
|
总结
- 使用
class
定义类,使用extends
继承React.Component
完成类组件定义 - 类名
首字母大写
,必须有render
函数返回 UI 结构,无渲染可返回null
- 使用的时候把类名当作
标签
使用即可
组件抽离
如果所有组件写在一个文件,代码写在一起后续会难以维护,组件作为一个独立的个体,一般都会放到一个单独的JS文件中。
抽离组件
- 定义一个
js
或者jsx
文件定义组件默认导出 - 使用组件导入即可,当作标签使用。
具体操作:
1.新建 src/components/Header.jsx
类组件,新建 src/components/Footer.jsx
函数组件
1 2 3 4 5 6 7
| import { Component } from 'react'; class Header extends Component { render() { return <div>头部组件</div>; } } export default Header;
|
1 2 3 4
| const Footer = () => { return <div>底部组件</div>; }; export default Footer;
|
2.新建 src/App.jsx
组件, 导入Header
Footer
组件使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import { Component } from 'react'; import Header from './components/Header.jsx'; import Footer from './components/Footer.jsx'; class App extends Component { render() { return ( <> <Header /> 内容 <Footer /> </> ); } }
|
3.index.js
使用 App
根组件
1 2 3
| import ReactDom from 'react-dom'; import App from './App.jsx'; ReactDom.render(<App />, document.getElementById('root'));
|
无状态组件和有状态组件
简单理解:
无状态(函数)组件,负责静态结构展示
有状态(类)组件,负责更新UI,让页面动起来
1.无状态组件
- 组件本身不定义状态,没有组件生命周期,只负责 UI 渲染。
React16.8
之前的函数组件都是无状态组件,Hooks
出现后函数组件也可以有状态。
2.有状态组件
- 组件本身有独立数据,拥有组件生命周期,存在交互行为。
class
组件可以定义组件自己的状态,拥有组件生命周期,它是有状态组件。
3.它们的区别
- 无状态组件由于没有维护状态只做渲染,性能较好。有状态组件提供数据和生命周期,能力更强。
4.如何去选择
React16.8
之前,组件不需要维护数据只渲染就使用函数组件
,有数据和交互使用类组件
。你需要去判断,有心智负担。React16.8
之后,Hooks
出现给函数提供状态,建议使用函数组件即可。
总结
- 组件本身没有状态就是无状态组件,组件本身提供状态就是有状态组件。
- 16.8 之前,无状态组件使用函数组件,有状态组件使用类组件。16.8 之后,统一可使用函数组件。
- React 没有说完全取代类组件,老项目中还是类组件居多,我们有必要学习下它的具体用法。
类组件 - 定义状态
- 定义
state
属性定义组件状态,属于组件自己的数据,它的值是个对象。 - 使用
state
的时候通过this
去访问即可,例如:this.state.xxx
。 - 数据发生变化,驱动视图更新。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import { Component } from 'react'; class App extends Component { state = { title: '数码产品', list: ['电脑', '手机', '相机'], }; render() { return ( <> <h3>{this.state.title}</h3> <ul> {this.state.list.map((item) => { return <li key={item}>{item}</li>; })} </ul> </> ); } } export default App;
|
总结:
- 定义
state
属性,值是对象存储数据,this.state.xxx
使用数据,数据驱动视图更新。
类组件 - 绑定事件
- 在类中声明事件处理函数,在标签上使用
on+事件名称={处理函数}
的方式绑定事件,事件名称需要遵循大驼峰
规则。 - 处理函数默认的参数就是事件对象,可以使用事件对象处理默认行为和事件冒泡。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| import { Component } from 'react'; class App extends Component { state = { count: 0, }; handleClick(e) { e.preventDefault(); e.stopPropagation(); console.log('handleClick'); } handleMouseEnter() { console.log('handleMouseEnter'); } render() { return ( <> <div onMouseEnter={this.handleMouseEnter}> 计数器:{this.state.count} </div> <div> <a href="http://www.iyouhun.com" onClick={this.handleClick}> 按钮 </a> </div> </> ); } } export default App;
|
总结:
- 绑定事件的方式和原生的方式一致,使用
on+事件名称={处理函数}
方式绑定 - 事件名称使用
大驼峰
规则,例如:onClick
onMouseEnter
, 处理函数默认传参为事件对象。
事件绑定this指向
- 在事件处理函数中打印
this.state.count
发现报错,this
是个undefined
。 - 演示函数调用对
this
指向的影响,得出函数谁调用 this
就执行谁。 - 找出原因:处理函数不是通过组件去调用的,导致出现
this
不是组件问题。
1.发现this是undefined
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { Component } from 'react'; class App extends Component { state = { count: 0, }; handleClick(e) { console.log(e); console.log(this.state.count); } render() { return ( <> <div>计数器:{this.state.count}</div> <div> <button onClick={this.handleClick}>按钮</button> </div> </> ); } } export default App;
|
2.演示处理函数调用对 this 的影响
1 2 3 4 5 6 7 8 9
| const obj = { name: 'tom', say() { console.log(this); }, }; obj.say(); const say = obj.say; say();
|
3.问题原因
- 类组件声明的处理函数,赋值给
on+事件名称
属性,调用的时候不是通过组件调用的。
处理 this 指向问题
- 通过绑定箭头函数解决 this 问题
- 通过 bind 解决 this 问题
- 通过声明箭头函数解决 this 问题
1.通过绑定箭头函数解决 this 问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Component } from "react"; class App extends Component { state = { count: 0, }; handleClick(e) { console.log(e) console.log(this.state.count) } render() { return ( <> <div>计数器:{this.state.count}</div> <div> + <button onClick={(e)=>this.handleClick(e)}>按钮</button> </div> </> ); } } export default App;
|
2.通过 bind 解决 this 问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Component } from "react"; class App extends Component { state = { count: 0, }; handleClick(e) { console.log(e) console.log(this.state.count) } render() { return ( <> <div>计数器:{this.state.count}</div> <div> + <button onClick={this.handleClick.bind(this)}>按钮</button> </div> </> ); } } export default App;
|
3.通过声明箭头函数解决 this 问题(推荐)
利用箭头函数形式的class实例方法。
注意:该语法是实验性语法,但是,由于babel的存在可以直接使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import { Component } from "react"; class App extends Component { state = { count: 0, }; + handleClick = (e) => { console.log(e) console.log(this.state.count) } render() { return ( <> <div>计数器:{this.state.count}</div> <div> <button onClick={this.handleClick}>按钮</button> </div> </> ); } } export default App;
|
类组件 - setState 使用
- React 类组件提供一个函数
setState({需修改数据})
,可以更新数据和视图。 - 直接修改 state 中的数据是不会更新视图,演示简单数据,数组,对象的正确修改方式。
1.通过setState的来修改数据更新视图
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import { Component } from 'react'; class App extends Component { state = { count: 0, }; handleClick = () => { this.setState({ count: this.state.count + 1, }); }; render() { return ( <> <div>计数器:{this.state.count}</div> <div> <button onClick={this.handleClick}>按钮</button> </div> </> ); } } export default App;
|
2.修改数组和修改对象的正确姿势
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| import { Component } from 'react'; class App extends Component { state = { count: 0, user: { name: 'jack', age: 18, }, list: ['电脑', '手机'], }; handleClick = () => { this.setState({ count: this.state.count + 1, }); }; updateList = () => { this.setState({ list: [...this.state.list, '相机'], }); }; updateUser = () => { this.setState({ user: { ...this.state.user, name: 'tony', }, }); }; render() { return ( <> <div>计数器:{this.state.count}</div> <div> <button onClick={this.handleClick}>按钮</button> </div> <hr /> <div>商品:{this.state.list.join(',')}</div> <button onClick={this.updateList}>改数组</button> <hr /> <div> 姓名:{this.state.user.name},年龄:{this.state.user.age} </div> <button onClick={this.updateUser}>改对象</button> </> ); } } export default App;
|
类组件 - 受控组件
1.什么是受控组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| import { Component } from 'react'; class App extends Component { state = { mobile: '13811112222', isAgree: true, }; changeMobile = (e) => { this.setState({ mobile: e.target.value, }); }; changeAgree = (e) => { this.setState({ isAgree: e.target.checked, }); }; render() { return ( <> <div> <input value={this.state.mobile} onChange={this.changeMobile} type="text" placeholder="请输入手机号" /> </div> <div> <input checked={this.state.isAgree} onChange={this.changeAgree} type="checkbox" /> 同意用户协议和隐私条款 </div> </> ); } } export default App;
|
总结:
- 使用
state
的数据赋值给表单原生,通过onChange
监听值改变修改 state 数据,完成表单元素的绑定。 - 这种表单元素称为受控组件。
类组件 - 非受控组件
1.什么是非受控组件?
- 没有通过 state 控制的表单元素,它自己控制自身的值,就是非受控组件
2.通过 ref 获取表单元素获取非受控组件的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import { Component, createRef } from 'react'; class App extends Component { mobileRef = createRef(); getMobile = () => { console.log(this.mobileRef.current.value); }; render() { return ( <> <div> {} <input ref={this.mobileRef} type="text" placeholder="请输入手机号" /> <button onClick={this.getMobile}>获取</button> </div> </> ); } } export default App;
|
总结:
ref
的作用:获取DOM或组件- 借助于
ref
,使用原生 DOM方式来获取表单元素值。
总结
1.组件的两种创建方式:函数组件和类组件
2.无状态(函数)组件,负责静态结构展示
3.有状态(类)组件,负责更新UI,让页面动起来
4.绑定事件注意this指向问题
5.推荐使用受控组件来处理表单
6.完全利用JS语言的能力创建组件,这是 React的思想