React中的Hooks

135 阅读3分钟

React v16.8之前,React函数组件又称为无状态组件。HookReact v16.8新出的特性,它可以允许我们在函数组件中使用state以及其他的React特性。即有了Hooks之后,函数组件也可以拥有和类组件一样的特性,如组件内的状态,生命周期等。

image.png


React中的Hooks

useState就是最常用的Hooks之一

在函数组件中,通过useState实现函数内部维护state,参数state默认的值,返回值为一个数组。第一个值为当前的num,第二个值为更新num的函数setNum。例如:

const [output, setOutput] = useState(props.value.toString());

useState不能局部更新。即更改其中一个属性时,需要先把其他不改的属性先拷贝进来。

image.png

useEffect可以理解为after render。


useEffect 也是常用的Hooks之一

useEffect第一个参数接受一个回调函数,默认情况下,即不传第二个参数。useEffect会在第一次渲染和更新之后都会执行,相当于在componentDidMountcomponentDidUpdate两个生命周期函数中执行第一个参数,即回调函数。当传入第二个参数时,只在第二个参数变化时执行回调函数。

  1. useEffect(fn1,[]) 模拟 componentDidMount;
  2. useEffect(fn2) 模拟 componentDidUpdate;
  3. useEffect(fn2,[n]) 也是模拟 componentDidUpdate;
  4. useEffect(()=>{ return fn3 }) 模拟 componentWillUnmount

useReducer 是useEffect的复杂版/进阶版

用法:
1.需要分别声明初始值state和操作reducer。
2.然后 const [formData, dispatch] = useReducer(reducer, initFormData);
3.声明一个读接口formData和一个写接口dispatch.
4.调用时写法:dispatch({ type: "xxx" });

import React, { useReducer } from "react";
import ReactDOM from "react-dom";

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

function reducer(state, action) {
    switch (action.type) {
        case "patch":
            return { ...state, ...action.formData };
        case "reset":
            return initFormData;
        default:
            throw new Error('类型出错');
    }
}

function App() {
    const [formData, dispatch] = useReducer(reducer, initFormData);
    // const patch = (key, value)=>{
    // dispatch({ type: "patch", formData: { [key]: value } })
    // }
    const onSubmit = () => {};
    const onReset = () => {
    dispatch({ type: "reset" });
    };
    return (
    <form onSubmit={onSubmit} onReset={onReset}>
        <div>
        <label>
        姓名
    <input
    value={formData.name}
        onChange={e =>
        dispatch({ type: "patch", formData: { name: e.target.value } })
    }
    />
    </label>
    </div>
    <div>
    <label>
    年龄
    <input
    value={formData.age}
    onChange={e =>
    dispatch({ type: "patch", formData: { age: e.target.value } })
    }
    />
    </label>
    </div>
    <div>
    <label>
    民族
    <input
    value={formData.nationality}
    onChange={e =>
    dispatch({
    type: "patch",
    formData: { nationality: e.target.value }
    })
    }
    />
    </label>
    </div>
    <div>
    <button type="submit">提交</button>
    <button type="reset">重置</button>
    </div>
    <hr />
    {JSON.stringify(formData)}
  </form>
 );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

useMemo用来优化渲染

当项目中存在多个组件,某个组件的参数变化,而其他组件的参数并没有变化时,组件全部重新渲染会占用资源,显然不合理。如果只更新数据变化的组件就好了。此时就可以用到useMemo。用法示例:

const onClickChild = useMemo(() => {
    const fn = div => {
      console.log("on click child, m: " + m);
    };
    return fn;
}, [m]);

useMemo(()=>{fn},[n]),第一个参数是需要保持不变的函数,第二个参数是依赖。只有当依赖变化时,才会重新执行fn。与Vue的computed类似。


useCallbackuseMemo的语法糖

两者功能完全一致。 image.png


useRef维持一个不变的n

如果需要一个值,在组件不断render时该变量保持不变(注意。不是指值不变,而是指地址),可以用useERef进行初始化达到这个目标。

import React from "react";
import ReactDOM from "react-dom";

const rootElement = document.getElementById("root");
function App() {
    const nRef = React.useRef(0);
    const log = () => setTimeout(() => console.log(`n: ${nRef.current}`), 1000);
    return (
        <div className="App">
          <p>{nRef.current} 这里并不能实时更新</p>
          <p>
          <button onClick={() => (nRef.current += 1)}>+1</button>
          <button onClick={log}>log</button>
          </p>
        </div>
        );
}

ReactDOM.render(<App />, rootElement);

原本根据React函数式编程的思想,每次render之后,会产生新的n。但使用useRef之后,n始终是同一个n,不会随着render而产生新的n来代替原来的n。n所代表的值存在nRef.current内。


useContext相当于加强版的useRef

useRef是在组件内保持一个变量不变,而useContext能够在一个范围内保持一个变量不变。用法:

const themeContext = React.createContext(null);

function App() {
    const [theme, setTheme] = React.useState("red");
    return (
      <themeContext.Provider value={{ theme, setTheme }}>
        <div className={`App ${theme}`}>
          <p>{theme}</p>
          <div>
            <ChildA />
          </div>
          <div>
            <ChildB />
          </div>
        </div>
      </themeContext.Provider>
);
}

function ChildA() {
  const { setTheme } = React.useContext(themeContext);
  return (
    <div>
      <button onClick={() => setTheme("red")}>red</button>
    </div>
  );
}

function ChildB() {
  const { setTheme } = React.useContext(themeContext);
  return (
      <div>
        <button onClick={() => setTheme("blue")}>blue</button>
      </div>
  );
}

此时,在themeContext.Provider标签内,useContext在A和B插件内都能生效。


自定义hooks

Hooks的使用更加简洁,减少了class组件中的this指向不明确的情况。每调用一次hooks,就会生成一份独立的状态。但是在某些情况下,还是需要自己定义hooks来实现一些功能。

如何构建一个hook,在第一次不更新,后续都更新的componentDidUpdate呢?

import {useEffect, useRef} from "react"; 

export const useUpdate=(fn:()=>void,deps:any[])=>{ 
    const count = useRef(0) 
    useEffect(()=>{ 
        count.current+=1 
       }) 
    useEffect(()=>{ 
        if(count.current>1){ 
            fn() 
            } 
         },deps) }

此时在其他位置导入 useUpadate并使用,就能达到第一次不更新,后续都更新的componentDidUpdate的效果。