【React】Hooks全解之逐个击破

·  阅读 855

Ⅰ. useState

在React函数组件中,是默认只有属性没有状态的,我们可以使用如下代码用useState读写变量和设定初始值 :

const [n,setN] = react.useState(0)

const [user,setUser] = react.useState({name:'Jack'})

💡 注意事项

  1. state不可以局部更新

我们无法局部setState,因为react函数组件不会帮我们合并属性。解决办法是使用react提供的...语法将原属性拷贝并放在更新值之前。

  1. set的对象必须是新对象

因为setState(obj)obj如果是在原来的对象上进行修改,那么对象的地址没有更改,react会将这个对象判定为原对象,则更改无效。解决方法是始终设置新的对象就可以了。

  1. useState接受函数,setState优先使用函数

① useState的初始值并不局限于对象,可以用函数的形式设定。

const [state, setState] = useState(()=>{ return initialState })

② 由于表达式的局限性,如果你需要在setState中进行多次操作,那么需要使用函数的形式以确保每一次的操作是有效的。

const onClick = ()=>{
    setN(n+1) //无效操作
    setN(n+1) //你会发现 n 不能加 2
  }
复制代码
const onClick = ()=>{
    setN(i=>i+1) //有效操作
    setN(i=>i+1) //最后值为n+2
  }
复制代码

Ⅱ.useReducer

如果你发现有几个变量需要汇总在一起,你可以使用复杂版的useState —— useReducer,它的好处就是可以对这个对象进行一个整体的操作 。并且可以践行React社区一直推崇的Flux/Redux思想,在我看来这个思想可能会在不久的将来Hooks思想盛行之后逐渐被取代,但是目前还是需要了解这个被广泛应用的Hook。

使用useReducer分为四个步骤:

  • 创建初始值initialState
  • 创建所有操作reducer(state,action)
  • 传给useReducer得到读和写API
  • 调用写({type:"操作类型"})

一个🌰:

const initial = {
    n:0
};

const reducer = (state,action) => {
    if(action.type === "add") {
        return { n:state.n + 1 };
    } else if (action.type === "multi") {
        return { n:state.n * 2 };
    } else {
        throw new Error("unknown type");
    }
  };
  function App() {
      const [state, dispatch] = useReducer(reducer, initial);
      const { n } = state;
      const onClick = () => {
          dispatch({ type: "add", number: 1 });
      };
      const onClick2 = () => {
          dispatch({ type: "multi", number: 2 });
      };
      return (
      <div className = "App">
        <div>n: {n}</div>
        <button onClick={onClick}>+1</button>
        <button onClick={onClick2}>*2</button>
      </div>    
    );
}
复制代码

如何代替redux?

  1. 将数据集中在一个store对象;
  2. 将所有操作集中在reducer;
  3. 创建一个Context;
  4. 创建对数据读写的API;
  5. 将第四步的操作放进Context中;
  6. 用Context.Provider将Context提供给i所有组件;
  7. 各个组件用useContext获取读写API。

Ⅲ.useContext

这个Hook翻译过来就是“上下文”。何为上下文?上下文就是你在运行一个程序的时候所要知道的所有变量。全局变量就是全局的上下文,上下文是局部的全局变量。上一个Hook中我们已经了解到useContext的用法。

一个🌰:

const C = createContext(null);

function App() {
  const [n, setN] = useState(0);
  return (
    <C.Provider value={{ n, setN }}>
      <div className="App">
        <Dog />
      </div>
    </C.Provider>
  );
}

function Dog() {
  const { n, setN } = useContext(C);
  return (
    <div>
      狗 n: {n} <Child />
    </div>
  );
}

function Child() {
  const { n, setN } = useContext(C);
  const onClick = () => {
    setN(i => i + 1);
  };
  return (
    <div>
      狗儿子 n: {n}
      <button onClick={onClick}>+1</button>
    </div>
  );
}
复制代码

Ⅳ.useEffect

这个Hook翻译过来为“副作用”,我认为把它叫做afterRender更好,因为这是每次render之后就会调用的一个函数。 用途:

  • 作为componentDidMount(出生后)使用

    useEffect(()=>{console.log('hi'),[]}) //使用空数组表示第一次渲染时

  • 作为componentUpdate(更新)使用

    useEffect(()=>{console.log('hi'),[n]}) //表示当{n}变化时

    useEffect(()=>{console.log('hi')}) //表示每一次更新时

  • 作为componentWillUnmount(将死)使用

    useEffect(()=>{console.log('hi')})
    return ()=>{} //添加return表示当组件将时执行某操作
    复制代码

useLayoutEffect

  • 布局副作用:useEffect在渲染后执行,useLayoutEffect在渲染前执行。
  • 特点: useLayoutEffect总是比useEffect先执行。
  • 经验: 为了用户体验,优先使用useEffect(优先渲染)。

Ⅴ.useMemo & useCallback

由于react默认会有多余的render出现,如果props不变就没要再次执行这个函数组件,useMemo就是阻止函数组件无效执行的Hook。

❗❗ 如果在函数组件中添加一个监听事件,则useMemo会直接无效,需要写成:

useMemo(()=> (x) => console.log(x))这种形式,一个返回函数的函数。 由于这种方式难用且难以理解,于是诞生了useCallback

useCallback(x => log(x), [m]) //等价于下面一种写法
useMemo(() => x => log(x), [m])
复制代码

Ⅵ.useRef

如果你需要一个值在组件不断render的时候保持始终是同一个值地址不变,那就需要用到useRef。

首先需要初始化:const n = useRef(0)

读取时则是:n.current (这里的current是用来保证两次useRef的是相同的值的引用)

❗❗ useRef是不能做到变化的时候自动render的,因为这不符合react的理念,如果你想要这个功能,完全可以自己加,监听ref,当ref.current变化的时候,调用setX即可。

Ⅶ.自定义Hook

自定义Hook必须以use开头,这是react既定的语法。通过自定义 Hook,可以将组件逻辑提取到可重用的函数中。

一个🌰:

function App() {
  const { list } = useList();
  return (
    <div className="App">
      <h1>List</h1>
      {list ? (
        <ol>
          {list.map(item => (
            <li key={item.id}>{item.name}</li>
          ))}
        </ol>
      ) : (
        "加载中..."
      )}
    </div>
  );
}
复制代码

与 React 组件不同的是,自定义 Hook 不需要具有特殊的标识。我们可以自由的决定它的参数是什么,以及它应该返回什么(如果需要的话)。换句话说,它就像一个正常的函数,我们可以最大限度的灵活使用这个Hook。

分类:
前端
标签:
分类:
前端
标签: