了解React Hooks

学习目标

  • 基础概念,重点是Hooks
  • 官方文档导读
  • 组件性能和组件开发检查表
  • 知识扩展
    • 单向数据流
    • useEffect和Class组件的生命期回调的关系
    • Hooks FAQ
    • Redux,重点是reducer的概念和useReducer的运用
  • 参考文献

基础概念

React框架

  • React框架只是一个View框架
  • 页面展示
    • 目标:展示后台JSON格式的View Model数据
    • JSON数据是树形结构,和组件树之间存在对应关系,参见React理念
    • 基本操作:获取后台数据,绑定到组件上

Virtual DOM

  • 解决问题:用户操作或后台数据推送导致数据变化,如何展示变化后的数据?
  • 最简单方案
    • 全部重绘
    • 存在问题:性能
  • 最理想方案
    • 仅局部重绘变化的部分
    • 关键问题:怎么知道哪部分变化了?
  • Virtual DOM概念
    • DOM的内存表示形式
    • 作用:比较Virtual DOM的差异,仅将差异部分更新到实际的DOM
  • JSX(JavaScript XML)
    • 什么是JSX
      <h1 className="greeting">
        Hello, world!
      </h1>
      
    • JSX是表达式,仅是一种Syntax Sugar,由Babel转换为
      React.createElement(
        'h1',
        {className: 'greeting'},
        'Hello, world!'
      )
      
    • 计算结果是JSON对象
      {
        type: 'h1',
        props: {
          className: 'greeting',
          children: 'Hello, world'
        }
      }  
      
    • 对应的HTML标记
      <h1 class="greeting">Hello, world</h1>
      
    • View Model和Virtual DOM的对应关系
      • View Model是树形的JSON数据
      • JSON数据和组件树存在对应关系
      • 因此:Virutal DOM的差异问题转化为JSON数据的差异问题

组件(Component)

  • React基于组件概念
    • 重复模式的标记子树可以抽象为“组件”
    • HTML页面也是一颗组件树
    • Web Component
      • 新的技术标准
      • 目前有四种方式
      • 在框架中作为实现技术
  • React组件
    • React组件相关的数据包括:
      • 属性(Properties)
        • 只读
        • 输入输出参数,其中输出参数是函数类型,可理解为回调函数向父组件传递结果
      • 状态(State)
        • 记录组件的内部状态
        • 使用useState Hook更改状态,因为涉及框架的界面更新逻辑
        • 注意:更改状态操作是异步的
      • 没有状态的组件是Stateless组件,也称为Presentational组件,有状态的也称为Container组件
    • 事件处理
    • 生命周期回调

数据不可变性(Immutability)

  • 解决问题:如何最有效地判断哪些数据已发生了变化?

  • 动画演示

    • 原数据没有发生变化,即不变性
    • 关键:只需比较引用,即可判断子对象是否发生了变化
  • 实现方案

    • 使用Object和Array的API实现

      • Oject实现示意
        • {...oldObject, updateKey: updateValue }
        • 相当于mutable的实现: oldObject.updateKey = updateValue;
      • Array实现示意
        • 增: [ newItem, ...oldArray ]
        • 改:Array.map(item => item.id === modified.id ? modified : item)
        • 删:Array.filter(item => item.id !== deletedId)
      • 说明
        • 理解在React中为什么要使用这样的实现方式?
        • JSON树较深时可读性差
    • Immer

      • 可以使用mutable API直接更改draft副本
      • 目前尚未在生成代码中使用

官方文档导读

Hello World

ReactDOM.render(
  <h1>Hello, world!</h1>,
  document.getElementById('root')
);
  • ReactDOM来自react-dom
  • <h1>Hello, world!</h1>是JSX表达式
  • document.getElementById是DOM API
  • 含义:在id="root"的元素位置展示以JSX表示的Virtual DOM
  • 实际应用程序中往往是展示顶层<App/>组件

JSX

  • 为避免不必要的<div>,JSX支持<Fragment>...</Fragment>,也可以写为<>...</>
  • 运行时选择类型
    const components = {
      photo: PhotoStory,
      video: VideoStory
    };
    
    function Story(props) {
      const SpecificStory = components[props.storyType];
      return <SpecificStory story={props.story} />;
    }
    

组件绘制

  • 在实际生产开发中,大多数React应用只会调用一次ReactDOM.render()
  • 函数式组件绘制、重绘的流程如下:
    • 调用组件函数返回JSX,即返回Virtual DOM的JSON数据表示形式
    • Reconciliation:比较和已有Virtual DOM的差异
    • Commit:将差异更新到DOM中
    • 其中第二和第三步由框架内部实现
  • 使用React Developer Tools Chrome插件

组件 & Props

  • 组件从概念上看就像是函数,接收任意的属性值(称之为"props"),返回在页面上展示的React元素
  • 组件定义
    • 使用函数方式定义组件
    • 函数定义的完整示例在src/components/example.tsx
      • 在TypeScript中定义组件,需增加properties的类型定义,注意是只读的
      • 使用React.memo()将组件指定为Pure,相当于使用类定义方式从React.PureComponent继承
        • 关键:只做浅层比较(shallow comparison): 值类型比较值,对象类型比较引用
        • State/Props是不可变对象
        • 所有子组件都是Pure的

State & 生命周期

  • 状态
    • 使用useState()定义状态
    const [count, setCount] = useState(0);
    
    • 如果计算状态的初始值开销很大,则可定义为arrow function
    const [count, setCount] = useState(() => heavyComputation());
    
    function Form() {
      // 1. Use the name state variable
      const [name, setName] = useState('Mary');
    
      // 2. Use an effect for persisting the form
      useEffect(function persistForm() {
        localStorage.setItem('formData', name);
      });
    
      // 3. Use the surname state variable
      const [surname, setSurname] = useState('Poppins');
    
      // 4. Use an effect for updating the title
      useEffect(function updateTitle() {
        document.title = name + ' ' + surname;
      });
    
      // ...
    }
    
    // ------------
    // 第一次render
    // ------------
    useState('Mary')           // 1. 将状态变量name初始化为'Mary'
    useEffect(persistForm)     // 2. 新增执行persistForm effect
    useState('Poppins')        // 3. 将状态变量surname初始化为'Poppins'
    useEffect(updateTitle)     // 4. 新增执行updateTitle effect
    
    // -------------
    // 第二次render
    // -------------
    useState('Mary')           // 1. 读取name变量 (参数被忽略)
    useEffect(persistForm)     // 2. 更新执行persistForm effect
    useState('Poppins')        // 3. 读取surname变量 (参数被忽略)
    useEffect(updateTitle)     // 4. 更新执行updateTitle effect
    
    // ...
    

事件处理

  • React元素的事件处理和DOM元素的很相似
  • 传入一个函数作为事件处理函数
  • 不能使用返回false的方式阻止默认行为,必须明确的使用preventDefault
  • 注意,使用arrow function避免bind(this)
  • 可以向事件处理程序传递参数

条件渲染

  • 可以使用以下方式条件渲染:
    • if语句
    • &&逻辑表达式
    • ? :三元表达式
  • 返回null不渲染

列表 & Keys

  • 使用Array.map渲染列表
  • 对于列表组件,每个组件增加唯一的key属性优化渲染
  • 由于JSX是表达式,Array.map中可以包括JSX

表单

  • 其值由React控制的输入表单元素称为“受控组件”,“非受控组件”的值保存在DOM对象中
  • 多个输入的解决方法

Context API

  • Context设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据
    • createContext创建
    • Context.Provider
    • 使用useContext使用Context

高级指引

  • Accessibility:尚未在产品中运用

  • Code-Splitting:v16.6的React.lazy()Suspend已运用

  • Context:使用useContext记录登录等基本全局信息

  • Error Boundaries:类似try…catch概念,已使用

  • Forwarding Ref:未使用

  • Fragments:<>...</>,推荐使用

  • 高阶组件: 优先考虑Hook实现

  • 和其他库集成:尚未集成第三方jquery组件

  • 深入JSX

  • 性能优化:使用create-react-app production

  • Portals:实现弹出窗口,待定

  • Profiler:暂未使用

  • 不使用 ES6:使用TypeScript

  • 不使用JSX:使用

  • 协调(Reconciliation): 了解对比算法

  • Refs & DOM:使用Refs可直接访问DOM,一般地不推荐使用

  • Render Props: 优先考虑Hook实现

  • 静态类型检查:使用TypeScript类型定义

  • strict mode:总是使用

  • PropTypes 进行类型检查:使用TypeScript类型定义

  • 非受控组件:使用ref,不推荐使用

  • Web Components:暂不使用

组件性能

目标

  • 提升性能的关键:
    1. 快速检测变化部分
    2. 减少不必要的重绘

快速检测

  • 快速检测变化的核心是使用Pure Component,即通过一次组件props引用的比较,即可判断数据是否发生了变化
  • 为实现该功能,数据必需是Immutable

减少重绘

  • 组件在以下两种情况下重绘:
    • 组件props发生了变化
    • 组件state发生了变化
  • 减少子组件的重绘
    • 使用Immutable数据减少不必要的数据变化,参见test-memo
    • 对于props中的callback函数,使用useCallback 记住能避免不必要的子组件重绘,参见test-useCallback
  • 对于列表,key属性必需设置为唯一的业务键以优化比较,不能设置为易变的下标值

检查表

  • 组件是否用作组件树中的叶节点?如果是,该组件是有状态的吗?建议叶节点组件是无状态的
  • 组件是否是Pure Component?即是否使用了React.memo?建议总是使用Pure Component
  • 列表组件的key属性是否是业务上唯一?注意不要使用index下标作为key
  • 组件状态的初始值是常量吗?建议使用常量,如果初始状态需跟着props改变,请使用useEffect设置状态
  • 组件状态的初始值的计算开销很大吗?如果是,请使用lambda表达式
  • 组件状态数据是否是Immutable的?建议总是使用Immutable数据
  • 组件的useState是否很多?如果是,建议使用useReducer封装状态逻辑
  • 组件状态改变时,各子组件是否会因为callback函数的变化而重绘?如果是,请使用useCallback
  • 组件状态改变时,其中是否有开销很大的计算步骤?如果有,请使用useMemo
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值