一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第7天,点击查看活动详情
7. React组件化开发
7.1 组件化开发思想:
- 核心思想:分而治之->抽象成一颗组件树
- 组件划分:页面->组件(功能块)->组件细分->组件可复用
- 组件划分:
- 函数组件/类组件
- 无状态组件/有状态组件
- 展示型组件/容器型组件
7.2 类组件
- 类组件定义要求:
- 组件名大写字符开头
- 继承自React.Component
- 必须实现render函数
- 其他可选:
- constructor: 初始化一些数据
- this.state: 维护组件内部数据
- notes: ES6之前支持creat-react-class模块定义类组件,官网推荐用class类定义
- render() 函数被调用时,会检查this.props和this.state的变化并返回值
- render返回值:
- React元素:通过JSX创建,渲染为DOM节点,渲染为组件
- 数组或fragments:使得render可以返回多个元素
- Portals: 可以渲染子节点到不同的DOM子树中
- 字符串或数值类型:在DOM中渲染成文本节点
- 布尔类型或null:不渲染
- React元素:通过JSX创建,
- notes:
- 利用ES7+Reacr插件快速插入:
- rcc -> create react-class-component
- rfc -> create react-function-component
- 利用ES7+Reacr插件快速插入:
7.3 函数组件
- 使用function定义,返回和render函数一样
- 特点:
- 没有生命周期也会被更新并挂载,但是没有生命周期函数
- 没有this
- 没有内部状态(state)
7.4 生命周期
- React生命周期阶段:
- 装载(Mount): 组件第一次在DOM树中渲染
- 更新(Update): 组件状态改变,重新渲染
- 卸载(Unmount): 组件被移出DOM树
- React生命周期函数:处于哪些阶段,组件内部实现回调函数
- componentDidMount函数:已挂载时回调
- componentDidUpdate函数:已更新时回调
- componentWillUnmount函数:被移除时回调
- 生命周期解析:LifeCycle图
- Mounting: constructor -> render -> React updates DOM and refs -> componentDidMount
- Updating: (New props、setState()、forceUpdate)-render -> React updates DOM and refs -> componentDidUpdate
- UnMounting: componentWillUnmount
- 常用生命周期函数:
- constructor: 通过this.state初始化内部state, 为事件绑定实例this
- compomentDidMount: 依赖于DOM操作,发送网络请求,添加订阅
- componentWillUnmount: 执行必要的清理操作,eg. 取消timer,取消网络请求,取消订阅
- 不常用生命周期函数:
- getDerivedStateFromProps: state值依赖于props,返回一个对象用于更新state
- getSnapshotBeforeUpdate: 在React更新DOM前回调函数,获取DOM更新前的信息(eg.滚动位置)
- shouldComponentUpdate: (常用)
7.5 组件的嵌套
- 组件核心思想对组件进行拆分,再将组件嵌套使用,组成了应用程序
- 嵌套逻辑:
- App组件 -> Header、Main、Footer
- Main组件 -> Banner、ProductList
7.6 组件间通信
- 父子间通信:
- 父组件 -> 子组件 :属性 = 值
- 子组件 -> 父组件 :props参数获取
- 子组件没有保存props至父组件,即参数为空super() -> react会自动保存,不影响
- 源码:react-test-renderer -> ReactShallowRenderer
- 参数验证:
- 项目中继承了Flow or TypeScript,可以直接验证类型
- 通过prop-types验证参数:
- MyComponent.propTypes = { key: propTypes.Type } // in function cpn
- static propTypes = { key: propTypes.Type } // in class cpn
- MyComponent.defaultProps = {set default value}
- React V15.5开始React.PropTypes移入:prop-types库中
- 其他验证:数组中包含哪些元素,对象包含哪些key,value是什么类型,某个原生是必须的
- defaultProps:没有传递时的默认值
7.7 Context
- 应用场景: 非父子(多个)组件数据共享,避免层层传递造成数据冗余
- 目的: 共享'全局'数据,例如用户、主题、首选语言...
- 类组件基本使用:
- 创建Context对象
- 将对象包裹在Context.Provider组件中,将属性传递的context赋值给value属性
- 将context赋值给需要使用Context的对象属性Class.contextType
- 利用this.context获取到Context对象属性值,注意:该组件必须为类组件(使用this.context属性)
- 函数组件基本使用:
- 创建Context对象
- 将对象包裹在Context.Provider组件中,将属性传递的context赋值给value属性
- 利用Class.Consumer组件获取context的value,value => {return(组件)}
- note:嵌套使用则需要嵌套Provider和嵌套Consumer使用,层叠使用获取值
- APIs:
- React.createContext: 创建Context对象
- const MyContext = React.createContext(defaultValue)
- 若组件订阅了一个Context,会从组件最近匹配的Provider中读取到当前的context值
- defaultValue是组件在顶层查找中没有找到对应的Provider时使用
- Context.Provider: Provider React组件
- <MyContext.Provider value = someValue>
- 每个Context对象均返回一个ProviderReact组件,允许消费组件订阅context变化
- 接收一个value属性,传递给消费组件
- 一对多个消费组件
- 可嵌套使用,里层覆盖外层数据
- value变化时,内部所有消费组件重新渲染
- Class.contextType: 重新赋值为新的Context对象
- MyClass.contextType = MyContext
- 使用this.context来消费最近的Context值
- 可以在任何生命周期访问,包括render函数
- Context.Consumer: React组件可以订阅到context变更,完成订阅
- <MyContext.Consumer>{value => {/* 基于context值进行渲染*/} }</MyContext.Consumer>
- 将函数作为子元素(function as child)
- 接收当前context值,返回一个Reacct节点
- React.createContext: 创建Context对象
7.8 setState
- 使用原因:修改state中的数据不会重新渲染界面
- 定义位置:setState() 是从Componen继承而来的
- 异步更新:
- 采用异步的原因:提升性能,避免频繁更新/渲染界面
- 问题:state和props不能保持一致性
- 若需要拿到修改后的同步数据:
- 通过setState({}, () => { 回调函数获取修改后值 })
- 通过生命周期函数componentDidUpdate() {获取修改后的值}
- 同步和异步的区分:
- 异步:组件生命周期和React合成事件中
- 同步:setTimeout或者DOM原生事件中
- 数据合并:
- Object.assign({}, prevState, partialState)
- 将之前的state和添加的部分state复制到新对象中
- setState操作合并:
- 基于数据合并的原理(assign),重复执行setState不会影响state结果,因为prevState还没改变,仅覆盖partialState的值,因此state不会改变
7.9 React更新机制
- React渲染流程:JSX -> virtualDOM -> realDOM
- React更新流程:
- props/state改变 -> render() -> new DOMTree ->
- 新旧DOM进行diff -> 差异更新 -> 更新到真实DOM
- 比较新/旧DOM树:
- 计算新旧树的差异:算法复杂度为O(n^3)
- 优化:
- 仅比较同层节点,不跨节点比较
- 不同类型节点产生不同树结构
- 通过key指定某些节点不同的渲染下保持稳定
- React更新:
- 对比得到元素类型更改:Reac将拆卸原有树,建立新树 (不会复用组件)
- componentWillUnmount() -> componentDidMount() -> render()
- 对比得到元素类型相同:React将保留DOM树,仅对比更改的属性
- update props -> componentWillRecieveProps() & componentWilUpdate() ->
- render() -> diff 递归(prevChanges, newChanges) TODO:子节点??
- 对子节点节点递归:
- React同时递归遍历两个DOM子节点,产生差异时生成一个mutation
- 若在中间插入/修改,则React对后续每一个子元素产生一个mutation -> 性能降低
- keys的优化:
- Warning: Each child in a list should have a unique 'key' prop
- 应用:
- 最后位置插入,引入key意义不大
- 前面、中间位置插入,仅做位移,不需要修改
- 注意:
- key应当是唯一的
- key不要使用随机数
- 使用index作为key,不能优化性能
- 对比得到元素类型更改:Reac将拆卸原有树,建立新树 (不会复用组件)
- render函数调用性能:
- 父组件render时,所有子组件均需要重新调用
- 通过生命周期函数阻断render更新:shouldComponentUpdate(nextProps, nextState) {return false//阻断}
- 通过继承React-PureComponent类:浅层比较pre和next是否一致,若未变化则不更新 TODO: 深比较?
其他:
- setState中不可改变state,否则引用地址不变,不能通过(newState === this.state)来判断state是否改变
- 全局事件传递:
- 事件总线:event bus
- 操作步骤:
- yarn add events : 安装库
- import {EventEmitter} from 'events' : 导入库
- const eventBus = new EventEmitter() : 创建事件发射器
- eventBus.emit(事件名: [String|Symbol], ...args: any[]): 发射事件和参数
- eventBus.addListner(事件名, handle): 添加事件监听,componentDidMount中添加
- eventBus.removeListner(事件名, handle): 添加事件监听,componentDidMount中添加
- note: handle(...args) : 参数为展开的形式,需要添加形参来接收