React的Hooks们

76 阅读3分钟

UseState

使用状态

const [n, setN] = React.useState(0);
const [user, setUser] = React.useState({ name: "Z" });

注意事项1:不可局部更新

如果State是一个对象,则无法完成局部更新 例如

function App() {
  const [user, setUser] = useState({ name: "Sam", age: 18 });
  const onClick = () => {
    setUser({
      name: "Jack",
    });
  };
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

image.png

当点击Click按钮时,页面上则只会显示Jack,18则会消失, 因为setState并不会帮助我们合并属性

image.png

注意事项2:地址要变

setState(obj)如果obj的地址不变,则React就认为数据没有发生变化,这和Vue3是不同的。

function App() {
  const [user, setUser] = useState({ name: "Sam", age: 18 });
  const onClick = () => {
    user.name = "Jack";
    setUser(user);
  };
  return (
    <div className="App">
      <h1>{user.name}</h1>
      <h2>{user.age}</h2>
      <button onClick={onClick}>Click</button>
    </div>
  );
}

点击按钮时,则不会发生变化,因为对象的地址没有发生变化,解决这一问题的方法写成注意事项1里的代码就好了。

useState接受函数

const [state, setState] = React.useState(() => {
  return initialState;
});
//该函数返回初始值state,且执行一次

setState接受函数

setN((i) => i + 1);

useReducer

用来践行Flux/Redux的思想

分四步走

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

const reducer = (state, action) => {
  if (action.type === "add") {
    return { n: state.n + action.number };
  } 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: "add", number: 2 });
  };
  return (
    <div className="App">
      <h1>n: {n}</h1>

      <button onClick={onClick}>+1</button>
      <button onClick={onClick2}>+2</button>
    </div>
  );
}

如何代替Redux

步骤

  • 将数据集中在一个store对象
  • 将所有操作集中在reducer
  • 创建一个Context
  • 创建对数据的读写API
  • 将上一步的API放入Context
  • 用Context.Provider将Context提供给所有组件
  • 各个组件用useContext获取读写API

useContext

useContext - CodeSandbox

上下文

全局变量是全局的上下文
上下文是局部的全局变量

使用方法

  • 使用C=createContext(initial)创建上下文
  • 使用<C.Provider>圈定作用域
  • 在作用域内使用useContext(C)来使用上下文

useContext注意事项

不是响应式的 在一个模块将C里面的值改变
另一个模块不会感知这个变化

useEffect

副作用

对环境的改变即为副作用,如修改document.title
但是我们不一定非要把副作用放在useEffect里

用途

作为componentDidMount使用,[]作第二个参数
作为componentDidUpdate使用,可指定依赖
作为componentWillUnmount使用,通过return
以上三种用途可同时存在

特点

如果同时存在多个useEffect,会按照出现次序执行

useLayoutEffect

布局副作用

useEffect在浏览器渲染完成后执行
useLayoutEffect在浏览器渲染之前执行
通过时间点侧面证明
useEffect与useLayoutEffect - CodeSandbox

特点

useLayoutEffect总是比useEffect先执行
useLayoutEffect里的任务最好影响了Layout
为了用户体验,优先使用useEffect

useMemo

useMemo - CodeSandbox

首先要理解React.memo

React默认有多余的多余的render
代码中的Child用React.memo(Child)代替
如果props不变,就没有必要再执行一次函数组件

但是会出现一个bug 在添加了监听函数之后,props不变时,还是会render函数租价
因为新旧函数虽然功能一样,但是地址不一样
使用useMemo

特点

第一个参数是()=>value
第二个参数是依赖,[m,n]
只有当依赖变化时,才会计算出新的value
如果以来不变,那么就重用之前的value

注意

如果需要的value是个函数,那么就要写成
useMemo(()=>(x)=>console.log(x))
这是一个返回函数的函数

useCallback

用法
useCallback(x=>log(x),[m])等价于
useMemo(()=>x=>log(x))

useRef

目的

如果你需要一个值,在组建不断render时保持不变
初始化:const count=useRef(0)
读取:count.current
为什么需要current?
为了保证两次useRef是同一个值(只有引用能做到)

forwardRef

useRef
可以用来引用DOM对象
也可以用来引用普通对象

forwardRef
由于props不包含ref,所以需要forwardRef

useImperativeHandle

暴露一个自定义ref
useImperativeHandle - CodeSandbox