响应式系统与 React | 青训营笔记

61 阅读4分钟

这是我参与「第四届青训营」笔记创作活动的的第4天

一个优秀的框架的实现,必然有属于它的开发初衷,可能是因为它解决了一些复杂且麻烦的问题,或者是因为能够为开发者带来便利,那么我们在学习新技术的时候,就很有必要了解这门技术的设计思路,这不仅对我们技术的提高有所帮助,同时也能让我们较快地从“懵”的状态切换到“清醒”的状态。

下面我们来看下 React 的设计思路是怎么一回事。

React 的设计思路

针对 UI 编程痛点:

  • UI 不会自动更新,需要手动调用 DOM 进行更新;
  • 欠缺基本的代码层面的封装和隔离,代码层面没有组件化。
  • UI 之间的数据依赖关系,需要手动维护,如果依赖链路长,则会遇到“Callback Hell”。

响应式与转换式:

什么是转换式系统:给定输入求解输出,比如编译器的实现,数值计算等场景。

image.png

什么是响应式系统:监听事件,由消息驱动,需要有一个监控系统去关注事件,并对事件做出响应,更新UI界面。

image.png

对于一个 web 应用,显而易见的,我们需要用响应式系统去实现,因为用户不可能手动输入参数并运行程序去求解输出,这也不符合我们的操作习惯。那么,响应式系统使用监听事件的方式,感知用户的操作行为,并响应它,使得页面达到用户的预期效果,且内部业务逻辑确保输出数据的准确性。

image.png

通过响应式编程,React 可以:

  • 状态更新,UI 也会进行更新
  • 前端代码组件化,可复用,可封装
  • 状态之间的互相依赖关系,只需声明即可

React hooks 的写法

函数组件:

import React, { useState } from 'react';

function Example() {
  // 声明一个新的叫做 “count” 的 state 变量  
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

State hook

const [count, setCount] = useState(0);

useState 就是一个 Hook0就是count的初始值,通过调用setCount可以更改count的值。

我们可以在一个组件中多次使用 State Hook:

import React, { useState } from 'react';

function Example() {
  // 声明多个 state 变量!
  const [age, setAge] = useState(42);
  const [fruit, setFruit] = useState('banana');
  const [todos, setTodos] = useState([{ text: 'Learn Hooks' }]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        Click me
      </button>
    </div>
  );
}

Effect Hook

useEffect(() => {    
    // code
});

当我们调用 useEffect 时,就是在告诉 React 在完成对 DOM 的更改后运行这个“副作用”函数。

副作用函数还可以通过返回一个函数来指定如何“清除”副作用:

useEffect(() => {    
    // 设置定时器
    
    return () => {      
        // 清除定时器
    }; 
});

跟 useState 一样,你可以在组件中多次使用 useEffect

生命周期

挂载阶段

当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:

  • constructor(): 在 React 组件挂载之前,会调用它的构造函数。
  • getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • componentDidMount(): 在组件挂载后(插入 DOM 树中)立即调用。

更新阶段

当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:

  • getDerivedStateFromProps(): 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。
  • shouldComponentUpdate():当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。
  • render(): render() 方法是 class 组件中唯一必须实现的方法。
  • getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
  • componentDidUpdate(): 在更新后会被立即调用。

卸载阶段

当组件从 DOM 中移除时会调用如下方法:

  • componentWillUnmount(): 在组件卸载及销毁之前直接调用。

diff 算法

diff 操作本身是有性能损耗的,React 文档中提到,即使在最前沿的算法中,将前后两棵树完全比对的算法的复杂程度为O(n³),其中n是树中元素的数量。

为了降低算法复杂度,React 的 diff 设置了三个限制条件,以确保复杂度能降为O(n)

  • 只对同级元素进行 diff 。如果一个DOM 节点在前后两次更新中跨越了层级,那么 React 不会复用该节点。
  • 只复用同类型的 DOM 节点。如果元素由div变为p,React 会销毁div及其子孙节点,并新建p及其子孙节点。
  • 通过元素的key属性判断是否可以复用。