7. React组件化开发

121 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 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:不渲染
  • notes:
    • 利用ES7+Reacr插件快速插入:
      • rcc -> create react-class-component
      • rfc -> create react-function-component

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

  • 应用场景: 非父子(多个)组件数据共享,避免层层传递造成数据冗余
  • 目的: 共享'全局'数据,例如用户、主题、首选语言...
  • 类组件基本使用:
    1. 创建Context对象
    2. 将对象包裹在Context.Provider组件中,将属性传递的context赋值给value属性
    3. 将context赋值给需要使用Context的对象属性Class.contextType
    4. 利用this.context获取到Context对象属性值,注意:该组件必须为类组件(使用this.context属性)
  • 函数组件基本使用:
    1. 创建Context对象
    2. 将对象包裹在Context.Provider组件中,将属性传递的context赋值给value属性
    3. 利用Class.Consumer组件获取context的value,value => {return(组件)}
    4. 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节点

7.8 setState

  • 使用原因:修改state中的数据不会重新渲染界面
  • 定义位置:setState() 是从Componen继承而来的
  • 异步更新:
    • 采用异步的原因:提升性能,避免频繁更新/渲染界面
    • 问题:state和props不能保持一致性
  • 若需要拿到修改后的同步数据:
    1. 通过setState({}, () => { 回调函数获取修改后值 })
    2. 通过生命周期函数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)
    • 优化:
      1. 仅比较同层节点,不跨节点比较
      2. 不同类型节点产生不同树结构
      3. 通过key指定某些节点不同的渲染下保持稳定
  • React更新:
    1. 对比得到元素类型更改:Reac将拆卸原有树,建立新树 (不会复用组件)
      • componentWillUnmount() -> componentDidMount() -> render()
    2. 对比得到元素类型相同:React将保留DOM树,仅对比更改的属性
      • update props -> componentWillRecieveProps() & componentWilUpdate() ->
      • render() -> diff 递归(prevChanges, newChanges) TODO:子节点??
    3. 对子节点节点递归:
      • React同时递归遍历两个DOM子节点,产生差异时生成一个mutation
      • 若在中间插入/修改,则React对后续每一个子元素产生一个mutation -> 性能降低
    4. keys的优化:
      • Warning: Each child in a list should have a unique 'key' prop
      • 应用:
        • 最后位置插入,引入key意义不大
        • 前面、中间位置插入,仅做位移,不需要修改
      • 注意:
        • key应当是唯一的
        • key不要使用随机数
        • 使用index作为key,不能优化性能
  • render函数调用性能:
    • 父组件render时,所有子组件均需要重新调用
    • 通过生命周期函数阻断render更新:shouldComponentUpdate(nextProps, nextState) {return false//阻断}
    • 通过继承React-PureComponent类:浅层比较pre和next是否一致,若未变化则不更新 TODO: 深比较?

其他:

  • setState中不可改变state,否则引用地址不变,不能通过(newState === this.state)来判断state是否改变
  • 全局事件传递:
    • 事件总线:event bus
  • 操作步骤:
    1. yarn add events : 安装库
    2. import {EventEmitter} from 'events' : 导入库
    3. const eventBus = new EventEmitter() : 创建事件发射器
    4. eventBus.emit(事件名: [String|Symbol], ...args: any[]): 发射事件和参数
    5. eventBus.addListner(事件名, handle): 添加事件监听,componentDidMount中添加
    6. eventBus.removeListner(事件名, handle): 添加事件监听,componentDidMount中添加
    7. note: handle(...args) : 参数为展开的形式,需要添加形参来接收