React

组件

组件允许你将UI拆分为独立可复用的代码片段,并对每个片段进行独立构思。

组件,从概念上类似于JavaScript函数。它接受任意的入参props,并返回用于描述页面展示内容的React元素。

React组件分为函数组件与class组件。

Hook

HookReact16.8 的新增特性。可以在不编写class的情况下使用state以及其他的React特性。

Hook为已知的React概念提供了更直接的APIpropsstatecontextrefs以及生命周期。

可以使用Hook从组件中提取状态逻辑,使得这些逻辑可以单独测试并复用。Hook可以在无需修改组件结构的情况下复用状态逻辑。这使得在组件间或社区内共享Hook变得更便捷。

  • 利于组件预编译,更易于优化
  • class组件会无意中鼓励开发者使用一些让优化措施无效的方案
  • class不能很好的压缩,并且会使热重载出现不稳定的情况

Hook是一些可以让你在函数组件里“钩入”React state及生命周期等特性的函数。

State Hook

useState通过在函数组件里调用它来给组件添加一些内部 stateReact会在重复渲染时保留这个state

不同于 class 组件state必须为一个对象,useState可以接受一个基本类型,也可以接受引用类型。

返回一对值:当前状态state和一个可以更新它的函数setState,可以在事件处理函数中或其他一些地方调用这个函数。

useState类似于class组件的this.setState,但是它不会把新的state和旧的state进行合并。

state不一定要是一个对象,且这个初始state参数只有在第一次渲染时会被用到

具体示例如下:

1import React, { useState } from "react";
2
3function Example() {
4  // 声明一个叫 “count” 的 state 变量。
5  const [count, setCount] = useState(0);
6
7  return (
8    <div>
9      <p>You clicked {count} times</p>
10      <button onClick={() => setCount(count + 1)}>Click me</button>
11    </div>
12  );
13}

React假设当你多次调用useState的时候,你能保证每次渲染时它们的调用顺序是不变的

Effect Hook

Effect Hook可以让你在函数组件中执行副作用操作,类似于componentDidMountcomponentDidUpdatecomponentWillUnmount这三个生命周期。

Effect Hook会在每次渲染后都执行。但是与componentDidMountcomponentDidUpdate不同,使用useEffect调度的effect不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。如果需要阻塞视图更新,可以使用useLayoutEffect Hook

React组件中有两种常见副作用操作:需要清除的和不需要清除的。需要清除的effect可以在useEffect中返回一个函数,React将会在执行清除操作时调用它。

React会在执行当前effect之前对上一个effect进行清除。

如果某些特定值在两次重渲染之间没有发生变化,可以通过传递数组作为useEffect的第二个可选参数,通知 React 跳过对 effect 的调用。

如果想执行只运行一次的effect(仅在组件挂载和卸载时执行),可以传递一个空数组[]作为第二个参数。

具体见下例:

1import React, { useState, useEffect } from "react";
2
3function FriendStatus(props) {
4  const [count, setCount] = useState(0);
5  useEffect(() => {
6    document.title = `You clicked ${count} times`;
7  }, [count]);
8  const [isOnline, setIsOnline] = useState(null);
9
10  useEffect(() => {
11    function handleStatusChange(status) {
12      setIsOnline(status.isOnline);
13    }
14    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
15    // 清除副作用
16    return function cleanup() {
17      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
18    };
19  }, [props.friend.id]); // 仅在 props.friend.id 发生变化时,重新订阅
20
21  if (isOnline === null) {
22    return "Loading...";
23  }
24  return isOnline ? "Online" : "Offline";
25}

Hook规则

Hook 就是JavaScript函数,但是要注意两点规则:

  • 只能在函数最外层调用Hook,不能在循环、条件判断或者子函数中调用。这样能确保Hook在每一次渲染中都按照同样的顺序被调用。让React能够在多次的useStateuseEffect调用之间保持hook状态的正确
  • 只能在React的函数组件中调用Hook,不要在其他 JavaScript函数中调用。另外自定义的Hook中也可以调用

React靠的是Hook调用的顺序来确定哪个state对应哪个useState

其他Hook

useContext接收一个context对象(React.createContext的返回值)并返回该context的当前值。

1const value = useContext(MyContext);

useReduceruseState的替代方案。接收一个形如(state, action) => newStatereducer,并返回当前的state 以及与其配套的dispatch方法。

1const [state, dispatch] = useReducer(reducer, initialArg, init);

useLayoutEffect其函数签名与useEffect相同,但它会在所有的DOM变更之后同步调用effect,会阻塞视图更新

自定义Hook

自定义Hook是一个函数,其名称以use开头,函数内部可以调用其他的Hook

React的生命周期

  • componentDidMount,组件第一次被渲染到DOM中的时候
  • componentWillUnmount,组件被删除的时候

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

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

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

  • static getDerivedStateFromProps()
  • shouldComponentUpdate()
  • render()
  • getSnapshotBeforeUpdate()
  • componentDidUpdate()

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

  • componentWillUnmount()

错误处理,当渲染过程,生命周期,或子组件的构造函数中抛出错误时,会调用如下方法:

  • static getDerivedStateFromError()
  • componentDidCatch()

Fiber

React架构

React 15架构可以分为两层:

  • Reconciler协调器:负责找出变化的组件
  • Renderer渲染器:负责将变化的组件渲染到页面上

React 16架构可以分为三层:

  • Scheduler调度器:调度任务的优先级,高优任务优先进入Reconciler
  • Reconciler协调器:负责找出变化的组件
  • Renderer渲染器:负责将变化的组件渲染到页面上