# React16基础知识
react 16
# 新特性
- 生命周期 
- 设为
UN_SAFE:componentWillMount、componentWillUpdate、componentWillReceiveProps - 推荐使用:
getDerivedStateFromProps、getSnapshotBeforeUpdate 
 - 设为
 - Hooks 
- 主要是解决
状态逻辑复用问题,将组件的 UI 与 状态 分离。 
 - 主要是解决
 - Context API 
- 新的Context API采用 声明式写法,并且可以透过 
shouldComponentUpdate返回 false 的组件继续向下传播 
 - 新的Context API采用 声明式写法,并且可以透过 
 - 代码分割 
React.lazy提供了动态 import 组件的能力,实现代码分割Suspense作用:在等待组件时 suspend(暂停)渲染,并显示加载标识
 - 错误处理机制 
- 新增生命周期
componentDidCatch 
 - 新增生命周期
 
# useCallback、useMemo的区别
useCallback一般会用来缓存函数的引用;useMemo缓存计算数据的值。
它们都是根据
依赖项去进行缓存。前者是缓存callback,后者是缓存callback执行后返回的值。
# useCallback
用法:
const handleCount = useCallback(() => setCount(count => count + 1), []) // 依赖项为空代表这个函数在组件的生命周期内都会 **永久缓存**
 # useMemo
用法:
const calcValue = useMemo(() => Array(10000).fill('').map(v => v), [count]); // 当count改变时,重新计算calcValue的值
 
useMemo一般会 用来去缓存需要进行大量计算量的函数。
# 通过Hooks获取上一个指定的prop值
// usePrevious.js
import { useRef, useEffect } from 'react';
const usePrevious = value => {
    const ref = useRef();
    useEffect(() => {
        ref.current = value;
    });
    return ref.current;
}
 2
3
4
5
6
7
8
9
10
11
使用时:
const prevCalcValue = usePrevious(calcValue);
 # 函数组件 与 class组件
| 方面 | 函数组件 | class组件 | 
|---|---|---|
| 定义方式 | 被定义为一个纯函数。 它接收一个props对象,并返回 React Element |  被定义为一个class。 它继承于 React.Component,并 通过render函数 返回React Element | 
| render行为 |  每次组件触发更新: 拥有完全独立的函数作用域, 返回相应的 React Element |  每次组件触发更新: 调用 render(),返回 React Element (对于同一处调用的class组件,只有一个class实例被创建/使用,后续的render只会改变this.props、this.state的值)  | 
| 状态 | 没有自身的state。 (在v16.8添加了hooks,可以使用useState钩子去管理state)  |  拥有自身的state | 
| 生命周期 | 没有生命周期。 (在v16.8后可通过 useEffect去模拟部分生命周期) |  有 | 
# React Hooks的优缺点
优点:
- 状态逻辑复用
- 通过组合
useState、useEffect等去实现状态逻辑复用、避免分散在各个生命周期中 
 - 通过组合
 - 函数式编程
 
缺点:
- 调用顺序:
- 要在组件的最顶层使用,不能在class组件、循环、条件、回调内使用Hooks
 
 - 依赖声明的关键性
- 部分Hooks需要根据依赖来判断是否需要重新渲染
 
 
# 函数式编程
函数式编程的特点:
- 函数是第一等公民
 - 没有副作用(不会影响外部变量)
 - 引用透明(输入相同,输出也相同)
 
# Hooks的管理机制
function PersionInfo ({initialAge,initialName}) {
    const [age, setAge] = useState(initialAge);
    const [name, setName] = useState(initialName);
    return (
        <>
            Age: {age}, Name: {name}
            <button onClick={() => setAge(age + 1)}>Growing up</button>
        </>
    );
}
 2
3
4
5
6
7
8
9
10
11
问题:
- 多个
useState,如何区分这两个状态? - 每次重新渲染,如何获取最新状态?
 - 为什么不能在 循环、条件、嵌套 里用 Hooks?
 - Hook的状态存在哪?
 
export type Hook = {
    memoizedState: any,
    baseState: any,
    baseUpdate: Update<any, any> | null,
    queue: UpdateQueue<any, any> | null,
    next: Hook | null, // 指向下一个Hook
}
 2
3
4
5
6
7
8
9
React认为Hook是一个链表(具有next属性)。 所以我们在组件里用到的Hooks是通过 链表 连接起来的(上一个Hook的next指向下一个Hook),这些Hooks节点利用单链表的结构串联在一起。
# 多个useState,如何区分两个状态?
 答案:初次渲染时,每次调用Hooks方法,就会调用mountState。它内部会通过mountWorkInProgressHook去创建一个 Hook节点 ,并把它添加到 Hooks链表 上。

# 每次重新渲染,如何获取最新状态?
答案:每个Hook节点都维护自身的 “更新链表(queue)”。通过queue来存放所有的历史更新操作。
在重新渲染时,会从 “更新链表(queue)” 的表头开始遍历,执行每一次更新,最后将最新的状态来返回,以保证每次重新渲染都能获得最新状态。


# 为什么不能在嵌套、条件、循环里用Hooks
答案:重新渲染时,每次调用Hooks方法,就会调用updateState。它内部会通过updateWorkInProgressHook去获取当前对应的链表节点(基于 初次渲染时生成的 Hooks链表的next)。
如果在条件语句中使用Hook,若不符合条件未执行对应useState,就会导致从 Hooks链表 中获取信息不正确。
初次渲染:state1 => hook1, state2 => hook2, state3 => hook3...
再次渲染:state1 => hook1, state3 => hooks2,乱套!
# Hook的状态存在哪?
答案:Hooks 链表会挂载到FiberNode.memoizedState
每个Fiber就是一个Virtual DOM,每个组件就对应Fiber树上对应的Fiber节点。
# 父Hook引用子Hook,它们内部Hooks之间的执行顺序?
// 父Hook
import React, { useEffect } from "react";
import useChild from "./useChild";
function App() {
  const { childName } = useChild("hsy"); // 会先输出子Hook内的useEffect
  useEffect(() => {
    console.log("App的effect");
  });
  return <div className="App">我是App,{childName}</div>;
}
 2
3
4
5
6
7
8
9
10
11
12
13
// 子Hook
import { useState, useEffect } from "react";
function useChild() {
  const [childName, setChildName] = useState("");
  useEffect(() => {
    console.log("useChild的useEffect");
    console.log(setChildName);
  }, []);
  return { childName };
}
export default useChild;
 2
3
4
5
6
7
8
9
10
11
12
13
14
15
正解: 父Hook调用子Hook,其实也是把子Hook当做一个函数被调用。(可以尝试将const { childName } = useChild('hsy');这行放到useEffect的下一行执行)
也就是说,将子Hook里的Hook“平摊”到子Hook被调用的地方,那随后会按按照类似“JS事件循环 - 微任务”的机制来处理父、子内部的这些Hook。
# 参考链接
← React基础知识 React Fiber →