【理论】React Hooks全解

  • Hook 是 React 16.8 的新增特性,所以使用Hook 时要保证React版本在16.8及以上。
  • npm info react versions ,用来获取react所有的版本号;
  • 有了Hook我们就不需要在class里面去声明状态了

useState

  • 具体内容可阅读上一节

useReducer

1、基础用法

  • 用来践行Flux/Redux的思想
  • 步骤:
    ①创建初始值initalState;
    ②创建所有操作reducer(state, action);
    ③传给useReducer,得到读写API;
    ④调用写({type: '操作类型'})
  • 可看下面代码:
import React, { useReducer } from "react";
import ReactDOM from "react-dom";

const initFormValue = {
  name: "",
  age: 18,
  nationality: "汉族"
};

function reducer(state, action) {
  switch (action.type) {
    case "patch":
      return { ...state, ...action.formData };
    case "reset":
      return initFormValue;
    default:
      throw new Error();
  }
}

function App() {
  const [formData, dispatch] = useReducer(reducer, initFormValue);
  const onReset = () => {
    dispatch({ type: "reset" });
  };

  return (
    
姓名 dispatch({ type: "patch", formData: { name: e.target.value } }) } />
年龄 dispatch({ type: "patch", formData: { age: e.target.value } }) } />
民族 dispatch({ type: "patch", formData: { nationality: e.target.value } }) } />

{JSON.stringify(formData)}
); } const rootElement = document.getElementById("root"); ReactDOM.render(, rootElement);

2、用useReducer代替Redux

  • 步骤
    ①将数据集中在一个store对象里;
    ②再将所有操作集中在reducer里;
    ③创建一个Context,const Context = React.createContext(null),一般都要传个null;
    ④将reducer、store传给useReducer,得到读和写API, const [state, dispatch] = useReducer(reducer, store)
    ⑤将第四步的内容放到第三步的Context里;
    ⑥用Context.Provider将Context提供给所有它所包住的组件,...
    ⑦各个组件用useContext获取读写API,const {state, dispatch} = useContext(Context),这里的Context是第三步里创建的Context;

总的来说,useReducer是useState的复杂版;

useContext

1、上下文

  • 上下文就是局部的全局变量;其实也就是作用域啦~能在哪个范围内使用;

2、使用方法

①先创建上下文,const C = React.createContext(null)
②使用圈定作用域,注意value里一般给一个读值,一个写值,例如:value={{n: n, setN: setN}}
③在作用域内使用const { n, setN } = useContext(C)来使用n和setN,注意:这里是从useContext解构,所以是{},不是[]

3、useContext不是响应式的

  • useContext改变数据的时候是自顶向下逐级改变而做到的,比如下方代码,在儿子中setN改变n,实际上是从App组件上,自上而下,把子组件中使用n的组件都渲染一遍,也就是说每次都会重新渲染一遍App
  • 而响应式则是,若爸爸中也使用了n,儿子中setN改变n,爸爸中的n会立即更新,不会通过App自上而下渲染一遍(vue3是用这种方法,响应式变化数据);
import { useContext, useState, createContext } from 'react';

const C = createContext(null);
function App() {
  const [info, setInfo] = useState(null);
  return (
    
      
); } function Baba() { return (
我是爸爸
); } function Son() { const { info, setInfo } = useContext(C); return (
我是儿子,info: {info}
); } export default App;

useEffect

1、副作用

  • 对环境的改变就叫副作用;比如修改document.title;
  • 实际上叫afterRender更贴切一些,因为是每次render后执行;

2、用途

useEffect(()=>{}, []),在第一次变化后执行,相当于componentDidMount;
useEffect(()=>{}, [n])只有在n变化的时候才执行(包含第一次渲染),相当于componentDidUpdate;
useEffect(()=>{}),任何一个变量变化就执行;
useEffect(()=>{.... return () => {}}),通过添加一个return来执行组件要消失时刻的操作,相当于componentWillUnmount;

上面这几种用途可同时存在;
可同时使用多个useEffect,它们之间是不冲突的;且是按照顺序来执行;

useLayoutEffect

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import "./styles.css";

const App = () => {
  const [value, setValue] = useState(0);
  
 // 这里再换成useLayoutEffect看有什么不同呢
  useEffect(() => {
    document.querySelector("#x").innerText = `value: 1000`;
  }, [value]);

  return (
    
setValue(0)}> value: {value}
); }; ReactDOM.render(, document.querySelector("#root"));

1、含义

  • useEffect是在浏览器渲染完成后执行;
  • useLayoutEffect是在浏览器渲染前执行;
  • 看上面代码,当为useEffect时,可以看到页面上,会先显示0再很快地换为1000;当换为useLayoutEffect时,你就看不到0的显示,会直接显示为1000;

2、特点

  • useLayoutEffect总是比useEffect先执行;
  • 如果要使用useLayoutEffect,那么useLayoutEffect里的任务最好是影响了layout;

3、总结

  • 所以为了用户体验,最好用useEffect;(useLayoutEffect会影响到用户看到画面变化的时间,因为用户就是想先看到画面啊!!!所以为什么非得要 useLayoutEffect在中间截胡一下,还要延迟用户看到画面的时间呢!)

useMemo

1、React.Memo()

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(0);
  const onClick = () => {
    setN(n + 1);
  };

  return (
    
); } function Child(props) { console.log("child 执行了"); console.log("假设这里有大量代码"); return
child: {props.data}
; }
  • 看上面代码,每次更新n的时候,即使m没有变化,Child组件也会跟着渲染一边,这多耗CPU啊,所以得想一个办法,m不变化的时候Child组件不跟着渲染;所以可以用React.Memo()把Child组件包起来,看下面代码~
const Child = React.memo((props) => {
  console.log("child 执行了");
  console.log("假设这里有大量代码");
  return 
child: {props.data}
; });

2、useMemo()用法

function App() {
  const [n, setN] = React.useState(0);
  const [m, setM] = React.useState(1);
  const onClick = () => {
    setN(n + 1);
  };

  const onClickChild = () => {
    console.log("点击child 了");
  };

  return (
    
); } const Child = React.memo((props) => { console.log("child 执行了"); console.log("假设这里有大量代码"); return
child: {props.data}
; });
  • 有一个bug,当你给Child组件添加了一个监听函数后,在改变n时,Child组件又跟着一起变了!!!这是因为App在运行时,onClickChild函数每次都会生成一个新的函数,新旧函数地址不一样!!!
  • 那怎么办呢?用React.useMemo()把onClickChild函数包起来!!!
 const onClickChild = React.useMemo(() => {
    return () => {
      console.log(m, "点击child 了");
    };
  }, [m]);

3、useMemo()的特点

  • 它的第一个参数是() => value,这个value可能是个函数也可能是个对象
  • 第二个参数是依赖[m, n];
  • 当依赖变化的时候就计算出新的value,如果依赖没有变化,就还用之前的value;
  • 这不就跟vue2的computed很像了嘛!!!

注意!如果你的value是个函数,你就要这么写,React.useMemo(()=> (x)=> console.log(x)),这是一个返回函数的函数,是不是很难用,所以就有了useCallback~~~

4、useCallback

  • 可以把最外层的那个函数省略掉,React.useCallback((x)=> console.log(x), [m]) 等价于 React.useMemo(()=> (x)=> console.log(x), [m])

useMemo与useCallback的区别:

  • useMemo主要用来缓存计算结果,为的是节省计算开销(跟vue2的computed很像)
  • useCallback主要用来缓存函数(也就是函数的引用地址),节省函数的不必要的重新创建(或刷新)开销;

useRef

1、目的

  • 当我们需要一个值,它需要在组件不断render的时候保持不变;
  • 初始化:const count = React.useRef(0)
  • 读取:count.current
  • 为什么需要current呢?因为这样就能保证每次渲染后的useRef都是同一个值(因为只有引用可以做到);

2、count.current的值变化时不会自动render

  • 如果想让count.current的值变化时可以自动render:
    调用一下setN();
    用vue3,vue3的ref改变会自动render,详见文档

forwardRef

1、什么时候用到它呢

  • 当父组件需要拿到子组件某个实际的DOM元素时需要用到它;
  • 当你要给函数组件上传一个ref属性,那么声明这个函数组件的时候,需要用forwardRef在外面套一层;
  • 并且套在forwardRef里面的函数组件会多接受一个参数ref,即里面的函数会接受两个参数:props和ref;
  • 为什么会有forwardRef呢?因为props不包含ref;那为什么props不包含ref呢?因为大多数时间都用不到;
  • 文档
import React, { useRef, forwardRef } from "react";

function App() {
//buttonRef 可以拿到子组件中真实的DOM元素
  const buttonRef = useRef(null);
  return (
    
      winwin
    
  );
}

const Button2 = forwardRef((props, ref) => {
  return ;
});

export default App;

useImperativeHandle

  • 名字起得不好,应该叫setRef;
  • 具体可看文档和PPT上的例子;

useState / useReducer ===> 它们里面的n每次都变;
useMemo / useCallback ===> 当依赖[m]变的时候,返回的值就变化;
useRef ===> 它的值永远不变;

自定义Hook

  • 通过自定义Hook,可以把组件逻辑提取到重用的函数里;
  • 自定义 Hook 是一个函数,其名称以 “use” 开头,函数内部可以调用其他的 Hook;
  • 封装数据操作,return出来增删改查的接口,例子,又或者因为它是一个函数,所以可以直接传参调用它;
  • 注意在用自定义Hook的时候,声明的时候是对象!!不是数组!!!const {list, setList} = useList(null)
  • 项目中尽量使用自定义Hook;

Stale Closure

  • Stale Closure:过时的闭包;用来描述你的函数引用的变量是之前产生的变量;
  • 看下这篇文章的几段代码,你就明白了;

总结

  • useContext,用来把读写接口给整个页面用;
  • useReducer,专门给Redux的用户设计的,我们甚至可以不用它;
  • useMemo,和React.Memo配合使用;
  • forwardRef并不是一个Hook;
image.png

你可能感兴趣的:(【理论】React Hooks全解)