React 组件化开发
无论是 vue、React 还是 Angular,主流框架都支持并提倡组件化开发,由于组件化开发不仅可以加强代码的能动性和复用性,还能够加快团队协作的速度。组件化开发就像搭积木,首先把一个个积木(组件)设计好,甚至将小积木(容器组件、展现组件)组装成具有肯定功能的积木(比方一个房子),最终再将功能化的积木摞成最终的成品(比方一个社区)。
本文简单详情 React 中组件的定义,以及容器组件、展现组件、高阶组件、复合组件等常见组件的应用,并详情组件间的通信方式。
1. 如何定义一个组件
1.1 一般组件
React 中组件的定义有两种方式,一种是使用 Class 关键字以类的形式来定义组件,另一种是使用函数方式定义。比方定义一个网站的欢迎提醒组件:
- 类定义
class WelcomeTip extends React.Component { render() { return ( <div> Welcome to this website! </div> ) }}- 函数定义
function WelcomeTip(props) { return ( <div> Welcome to this website! </div> )}无论使用哪一种方式定义组件,组件的调用都是一致的
<WelcomeTip></WelcomeTip>但是,组件内状态管理、生命周期却有着很大的不同,本文中主要采用类定义的方式来构建组件,关于函数定义组件的应用可以移步 “React Hook” 的详情。
- 组件状态
class Counter extends React.Component { // 写了 constructor 就要调用 super constructor(props) { super(props) // 状态公告 this.state = { count: 0 } } // state 的调用:this.state.xxx // state 的修改:this.setState({count: 1}) // 或者者 this.setState(state => ({count: 1})) // 支持同时设置多个 key 值,key 值相同时后者覆盖前者 // setState 是一个异步函数 render() { return ( <div> <p>Welcome, {this.props.name}! You have click {this.state.count} times!</p> <button onClick={() => this.setState(state => {count: state.count + 1})} >Click</button> </div> ) }}- 组件的生命周期
- 初始化:
constructor,用于完成组件的初始化工作,如定义state的初始内容、定义组件内部变量等 - 组件的挂载:
componentWillMount,发生在组件挂载到 DOM 之前,此处修改state不会引起组件的重新渲染。该部分的功能也可以提前到constructor中,因而很少在项目中使用。render,根据组件的props和state(两者的重传递和重赋值,无论值能否有变化,都可以引起组件重新 render),返回?个 React 元素(形容组件,即UI),不负责组件实际渲染?作,之后由 React ?身根据此元素去渲染出??DOM。纯函数,返回结果只依赖于传入的参数,执行过程中没有反作用。不能在该阶段执行setState,会造成死循环。componentDidMount,组件挂载到 DOM 之后调用,且只会被调用一次。
- 组件的升级:当
props或者state被重新赋值时,无论值能否发生改变,都会触发组件的升级。因而有如下两种情况会触发组件的升级:1. 父组件重新 render,因为子组件的props被传值,触发子组件的升级;2. 组件本身调用setState,无论state有没有改变,组件都会升级componentWillReceiveProps(nextProps),props重传时被调用,该函数中调用setState不会引起组件的二次升级,因而即使在该函数中执行this.setState升级了state,shouldComponentUpdatecomponentWillUpdate中的this.state仍旧是原来的值。shouldComponentUpdate(nextProps, nextState),此?法通过?较nextProps,nextState及当前组件的this.props,this.state,返回true时当前组件将继续执?升级过程,返回false则当前组件升级停?,以此可?来减少组件的不必要渲染,优化组件性能。componentWillUpdate(nextProps, nextState),此?法在调?render?法前执?,在这边可执??些组件升级发?前的?作,?般较少?。render:同挂载时的 render。componentDidUpdate(prevProps, prevState),此?法在组件升级后被调?,可以操作组件升级的 DOM ,prevProps和prevState这两个参数指的是组件升级前的props和state。
- 组件的卸载:
componentWillUnmount:此?法在组件被卸载前调?,可以在这?执??些清除?作,?如清理组件中使?的定时器,componentDidMount中?动创立的 DOM 元素等,以避免引起内存泄漏。
- 【注意】
componentWillMountcomponentWillReceiveProps和componentWillUpdate在 React 17.x 版本之后将不再支持,目前使用会提醒 warning。在 16.3 之后,使用getDerivedStateFromProps代替上述三个函数static getDerivedStateFromProps(props, state),在组件创立时和升级时的 render ?法之前调?, 它应该返回?个对象来升级状态,或者者返回 null 来不升级任何内容。getSnapshotBeforeUpdate,被调?于render之后,此时可以读取但还不能操作升级 DOM ,因而可以按需调整滚动条等。 返回值(必需有)将作为参数传递给componentDidUpdate。
引自 aermin/blog/issues/55
- 初始化:
1.2 组件拆分——容器组件&展现组件
在涉及复杂的数据预解决时,可以考虑将组件拆分成容器组件和展现组件。其中容器组件负责请求并解决数据,展现组件负责根据 Props 显示信息。如此可以减小组件的体积,使开发人员可以跟专注于某一功能开发,并提高组件的重用性和可用性,同时易于测试和提高系统性能。
// 容器组件class CommentList extends React.Component { state = { list: [] } componentDidMount() { setTimeout(() => { this.setState({ list: [ {id: 1, text: '我喜欢苹果', author: '小A'}, {id: 2, text: '我喜欢橙子', author: '小B'}, {id: 3, text: '我喜欢西瓜', author: '小C'}, ] }) }) } render() { return ( <div> {this.state.list.map(l => { return <Item key={l.id} text={l.text} author={l.author}/> })} </div> ) }}// 展现组件function Item({text, author}) { return (<div> {text} -- <span style={{color: 'blue'}}>{author}</span> </div>)}1.3 PureComponent
在组件生命周期中组件升级过程中,提及只需发生重新挂载,无论 props state 能否变化,都会出发升级。纯组件就是定制了 shouldComponentUpdate 后的Component,仅有依赖的数据发生变化时才进行升级。 该比较过程数据浅比较,因而对象属性或者数组中元素并不适用于该特性。
// 假设父组件有 count 和 name 两个状态// 子组件仅依赖父组件的 count// 假如子组件继承的是 React.Component,那么父组件 name 值发生变更时,子组件仍旧会重新 render// 继承的是 React.PureComponent 时,则仅有父组件的 count 值变化时,子组件才会重新调用 render class Child extends React.PureComponent { render() { return <div>{this.props.count}</div> }}React 16.6.0 之后,使用 React.memo 让函数式的组件也有 PureComponent 的功能
const Child = React.memo(() => { return <div>{this.props.count}</div>})2. 高阶组件是什么
2.1 高阶组件与一般组件有什么不同
高阶组件是 React 中重用组件逻辑的高级技术,它不是 React 的 api ,而是一种组件加强模式。高阶组件是一个函数,它返回另外一个组件,产生新的组件可以对被包装组件属性进行包装,也可以重写部分生命周期。
高阶组件可以为组件增加某一特殊功能,也可以多层嵌套,赋予被包装组件多个功能。比方打印日志功能、增加标题功能等。
// 包装后的组件具有日志打印功能const withLog = Component => { class newComponent extends React.Component { componentDidMount() { console.log(`${Date.now()}:组件已挂载`) } render() { return <Component {...this.props} /> } } return newComponent}// 包装后的组件都带有一个标题const withTitle = Component => { const newComponent = props => { return (<Fragment> <h3>这是一个标题</h3> <hr /> <Component {...props} /> </Fragment>) } return newComponent}2.2 高阶组件怎样使用
- 链式调用
高阶组件本质上就是一个函数,因而可以采用链式调用的形式,将待包装的组件作为参数传入,并 export 出去就可。同时也可以多个高阶组件嵌套,一层层包装单一组件。
export default withLog(withTitle(CommentList))- 装饰者模式
ES7 中提供了装饰者模式的写法,可以使代码更加简洁,但需要进行相关配置:
暴露项目的所有配置项:
npm run eject安装:
npm install -D @babel/plugin-proposal-decorators配置
package.json文件中 babel 配置项"babel": { "presets": [ "react-app" ], "plugins": [ ["@babel/plugin-proposal-decorators", {"legacy": true}] ] }
如此,上述链式调用可以修改为:
export default @withLog@withTitleclass CommentList extends React.Component { ...}3. 复合组件
复合组件可以让开发者以更便捷地创立组件的外观和行为,相比继承更加直观和安全。
// 容器不关心内容与逻辑// 3. 容器中可以使用 children,但因为传入的是 vdom 数组,故而不能修改function Dialog(props) { return (<div style={{border: `1px solid ${props.color || '#ccc'}`}}> {React.Children.map(props.children, child => child.type === 'p' ? child : null)} {props.footer} </div>)}// 通过复合提供内容function HelloDialog(props) { // 1. 参数可以使用 props 传入 // 2. 可以传入任何表达式 return (<Dialog color='blue' footer={<p>版权归 road 所有</p>}> <h3>你好啊,{props.name}</h3> <p>感谢访问本网站</p> </Dialog>)}4. 组件间如何实现通信
4.1 父传子
通过 props 将参数传递给子组件,使用 class 关键字以类方式定义组件时,使用 this.props 就可以父组件传递的所有参数,函数方式定义时则需要在公告时增加 props 参数,或者解构参数。
// 类方式定义class Child extends React.Component { render() { return (<div> 子组件:{this.props.name} </div>) }}// 函数方式定义function Child(props) { return (<div> 子组件:{props.name} </div>)}// 函数方式function Child({name}) { return (<div> 子组件:{name} </div>)}父组件传参:
<Child name='road'></Child>4.2 子传父
父组件中公告一个相关方法,并作为参数传递给子组件。子组件通过调用父组件传递过来的方法,修改父组件中的数据。
// 比方:父组件中有个计数值,子组件中的按钮点击之后计数值 +1function Child({increase, step}) { return ( <div> <button onClick={() => increase(step)}>+{step}</button> </div> );}export class Parent extends Component { state = { count: 0 } add(step) { this.setState(state => ({count: state.count + step})) } render() { return ( <div> 计数值为 {this.state.count} {/* 注意方法传递过程中 this 的指向变更 */} <Child increase={this.add.bind(this)} step={1}></Child> <Child increase={this.add.bind(this)} step={2}></Child> </div> ); }}4.3 跨组件通信
跨组件通信有兄弟组件通信、父组件与孙组件的通信等,从上到下的数据传递可以通过 props 一层层传递,但从下到上的数据传递则十分麻烦。例如下图中【子组件1】相与【父组件B】通信时,就需要将信息一层层冒到祖先组件中,再通过祖先组件派发给【父组件B】。
多层组件结构
因而假如项目较为庞大时,可以引入 redux 进行全局状态管理(可参考 redux 使用实例)。当项目量级较小时,则使用 React 中的 Context 来进行公共状态的管理,该模式包括两个角色:
Provider:外层提供数据的组件,内部组件都可以访问到来自 provider 的数据
Consumer :内层获取数据的组件,沿上追溯到最近的 provider,消费其数据。接收一个函数作为子节点,返回 react 节点。
function Display(props) { // 6. props 重新赋值,组件升级 return ( <div> <h2>{props.title}</h2> <p>你的名字是:{props.name}</p> <p>你的邮箱是:{props.email}</p> </div> )}class FormItem extends Component { state = { val: '' } render() { const {keyName, label, type} = this.props // 3. consumer 内部接收一个函数,参数 value 来源于最近的 provider return (<SurveyContext.Consumer> {(value, _this) => { return (<div> <label htmlFor={keyName}>{label}</label> <input id = {keyName} type = {type} placeholder={value[keyName]} onChange = {e => {this.setState({val: e.target.value})}} onKeyDown = {e => { if( 13 === e.keyCode ) { // 4. 调用操作方法,也即 Survey 组件中的 changeState 方法,修改 provider 中的数据 value.change(keyName, this.state.val) } }} /> </div>) }} </SurveyContext.Consumer>) }}// 2. 中间组件不需要传递数据和方法class Form extends Component { render() { return ( <div> <FormItem keyName='name' label='名字' type='text'/> <FormItem keyName='email' label='邮箱' type='text'/> </div> ); }}const SurveyContext = React.createContext()export default class Survey extends Component { state = { name: 'abc', email: '123@163.com' } changeState(key, val) { this.setState({[key]: val}) } // 5. setState 方法触发组件升级,重新 render render() { return ( <div> {/* 1. provider 提供 value 给 consumer,可以将修改 state 的方法也作为 value 对象的方法传递*/} <SurveyContext.Provider value={{ ...this.state, change: this.changeState.bind(this) }} > <Form></Form> </SurveyContext.Provider> <hr /> <Display title='问卷调查' name={this.state.name} email={this.state.email}></Display> </div> ) }}效果:
跨组件通信实例
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » React 组件化开发