美文网首页
【理论】React Hooks全解

【理论】React Hooks全解

作者: darkTi | 来源:发表于2022-02-16 17:15 被阅读0次
  • 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 (
    <div>
      <form onReset={onReset}>
        <div>
          姓名
          <input
            value={formData.name}
            onChange={(e) =>
              dispatch({ type: "patch", formData: { name: e.target.value } })
            }
          />
        </div>
        <div>
          年龄
          <input
            value={formData.age}
            onChange={(e) =>
              dispatch({ type: "patch", formData: { age: e.target.value } })
            }
          />
        </div>
        <div>
          民族
          <input
            value={formData.nationality}
            onChange={(e) =>
              dispatch({
                type: "patch",
                formData: { nationality: e.target.value }
              })
            }
          />
        </div>
        <div>
          <button type="submit">提交</button>
          <button type="reset">重置</button>
        </div>
        <hr />
        {JSON.stringify(formData)}
      </form>
    </div>
  );
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, 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提供给所有它所包住的组件,<Context.Provider value={{state, dispatch}}>...</Context.Provider>
    ⑦各个组件用useContext获取读写API,const {state, dispatch} = useContext(Context),这里的Context是第三步里创建的Context;

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

useContext

1、上下文

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

2、使用方法

①先创建上下文,const C = React.createContext(null)
②使用<C.Provider value={}></C.Provider>圈定作用域;
③在作用域内使用useContext(C)来使用上下文;

3、useContext不是响应式的

  • useContext改变数据的时候是自顶向下逐级改变而做到的(每次都会重新渲染一遍App),不是监听这个数据变化,通知这个组件去变化的(vue3是用这种方法,响应式变化数据);

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 (
    <div id="x" onClick={() => setValue(0)}>
      value: {value}
    </div>
  );
};

ReactDOM.render(<App />, 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 (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child data={m} />
    </div>
  );
}

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

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 (
    <div className="App">
      <div>
        <button onClick={onClick}>update n {n}</button>
      </div>
      <Child data={m} onClick={onClickChild} />
    </div>
  );
}

const Child = React.memo((props) => {
  console.log("child 执行了");
  console.log("假设这里有大量代码");
  return <div onClick={props.onClick}>child: {props.data}</div>;
});
  • 有一个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])

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、什么时候用到它呢

  • 如果你希望一个函数组件支持ref属性,那么你就需要拿forwardRef把这个函数组件给包起来;
  • 当你要向你的子组件传递ref引用时,就要用到forwardRef(初步理解,使用过程中有其他感悟再来添加~~);
  • 因为props不包含ref;那为什么props不包含ref呢?因为大多数时间都用不到;
  • 文档

useImperativeHandle

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

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

自定义Hook

  • 封装数据操作,return出来增删改查的接口,例子
  • 项目中尽量使用自定义Hook;

Stale Closure

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

总结

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

相关文章

  • 【理论】React Hooks全解

    Hook 是 React 16.8 的新增特性,所以使用Hook 时要保证React版本在16.8及以上。npm ...

  • React Hooks

    React Hooks Hooks其实就是有状态的函数式组件。 React Hooks让React的成本降低了很多...

  • react-hooks

    前置 学习面试视频 总结react hooks react-hooks react-hooks为函数组件提供了一些...

  • React Hooks

    前言 React Conf 2018 上 React 提出了关于 React Hooks 的提案,Hooks 作为...

  • 5分钟简单了解React-Hooks

    首先附上官网正文?:React Hooks Hooks are a new addition in React 1...

  • react-hooks

    react-hooks react-hooks 是react16.8以后,react新增的钩子API,目的是增加代...

  • React-hooks API介绍

    react-hooks HOOKS hooks概念在React Conf 2018被提出来,并将在未来的版本中被...

  • React Hooks 入门

    React Hooks 是 React v16.8 版本引入了全新的 API。 React Hooks 基本概念 ...

  • react hooks 源码分析 --- useState

    1. react hooks简介 react hooks 是react 16.8.0 的新增特性,它可以让你在不编...

  • React Hooks的入门简介

    什么是React Hooks? 首先React Hooks是React生态圈里的新特性,它改变了传统react的开...

网友评论

      本文标题:【理论】React Hooks全解

      本文链接:https://www.haomeiwen.com/subject/mycgzltx.html