vivipure / TIL

Today I learned
0 stars 0 forks source link

React hooks #17

Open vivipure opened 2 years ago

vivipure commented 2 years ago

useId

useIdReact 18 新增的hooks, 可以生成独一无二的id。

当我们的组件html 里面包含 id 时,多个组件同时渲染,会表现为多个元素拥有同一个id,导致语法上不合理。虽然我们可以用随机数或者时间戳的方式生成id, 但是在函数组件中每次更新都会更改id,所以官方出了 useId.

官方的文档也说明了 useId 出现的作用

  1. 当第三方库需要独一无二的 id时
  2. 处理服务端渲染后,和客户端 hydrate 不匹配的问题

具体使用:

function Checkbox() {
  const id = useId();
  return (
    <>
      <label htmlFor={id}>Do you like React?</label>
      <input id={id} type="checkbox" name="react"/>
    </>
  );
};

这里比较有意思的是,生成的 id 并不是合法的 selector id, 也就是说无法通过 DOM 匹配都该元素,毕竟 React 推荐使用 ref 来操作操作 DOM

vivipure commented 2 years ago

useState

useState可以传入一个初始值,然后返回一个值和一个改变值方法的数组

const [count, setCount] = useState(0)

使用 setMethod 会触发组件进行更新,组件函数会重新执行。

这里需要注意:

  1. 如果传入的值和之前的值相等,则不会触发重新更新
  2. 一个逻辑里多次触发 setState, 只会触发最后一个
  3. 执行 setState 函数后,当前 state 的值依旧是旧的值,各种闭包使用的都是旧值。这个符合 React 的预期,每次组件更新都会重新执行函数,产生新的值
  4. 引用类型的值,直接更改值组件不会更新。将当前值更改然后传入 setState 函数,也不会触发更新。因为引用类型地址一样,比较相同无法触发更新。所以对于引用类型需要将值进行拷贝后再进传入
  5. 不能在条件语句中使用
vivipure commented 2 years ago

useEffect

我们在组件中请求数据,更改DOM或者其他的操作逻辑,都是 side effect . 这些 effect 会影响到其他组件,而且在组件渲染时无法正常完成。

useEffect -- 副作用的hook。每当我们更改数据,或者组件状态变化时想要进行特定的逻辑,都可以使用 useEffect.

useEffect 拥有两个参数,一个是副作用的回调,一个是依赖项。每当依赖项更改时,副作用回调都会重新执行

useEffect 的依赖传入不同的值会有不同的作用。相当于 Class 组件的 componentDidMount, componentDidUpdate, componentWillUnmount.

  1. 依赖传空数组,相当于 componentDidMount,只会执行一次
  2. 依赖不传,相当于 componentDidUpdate,每次组件更新都会执行
  3. 回调函数返回一个函数,且无依赖项 相当于 componentWillUnmount

注意点:

  1. 当 useEffect 内部使用到其他值时记得将其添加到依赖,避免闭包陷阱
  2. 绑定事件,订阅,定时器等操作,记得在返回函数进行取消

当我们开发时有安装 lint 时,如果 useEffect 传空数组会进行格式提醒的。但是业务场景只需要组件挂载时才执行,而不是每次更新, 这样很容易导致死循环。这个时候我们需要结合其他 hook 一起使用。

vivipure commented 2 years ago

useContext

使用 useContext 可以让我们使用 createContext 中定义Context。

const themes = {
  light: {
    foreground: "#000000",
    background: "#eeeeee"
  },
  dark: {
    foreground: "#ffffff",
    background: "#222222"
  }
};

const ThemeContext = React.createContext(themes.light);

function App() {
  return (
    <ThemeContext.Provider value={themes.dark}>
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar(props) {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  const theme = useContext(ThemeContext);
  return (
    <button style={{ background: theme.background, color: theme.foreground }}>
      I am styled by theme context!
    </button>
  );
}

useContext(MyContext) 会在当前组件的祖先组件中,找到离自己最近的MyContext.Provider, 获取其中的值,没有则使用默认值。

每当组件使用的context 的值变化时,组件就会重新渲染。

相比于 Class 组件,useContext 可以不需要使用 MyContext.consumer 来获取值。

在简单的业务场景,可以通过这个 hook 进行状态管理,推荐使用 unstated-next 来增强 context 的能力

vivipure commented 2 years ago

useReducer

const [state, dispatch] = useReducer(reducer, initialArg, init);

传入 reducer和初始化的值,第三个参数式很少使用,用来返回默认值,可以根据使用场景来处理默认值

返回状态和 dispacth 函数,可以通过 dispatch reducer中定义操作来更改状态

function init(initialCount) {
  return {count: initialCount};
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return {count: state.count + 1};
    case 'decrement':
      return {count: state.count - 1};
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({initialCount}) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({type: 'reset', payload: initialCount})}>
        Reset
      </button>
      <button onClick={() => dispatch({type: 'decrement'})}>-</button>
      <button onClick={() => dispatch({type: 'increment'})}>+</button>
    </>
  );
}

需要注意的是, 和 reducx 使用一样,更改值时不能直接对引用进行更改,应该返回一个新的状态。

useReducer 可以和 useContext 一起处理比较复杂的业务状态,进行状态管理。

vivipure commented 2 years ago

useMemo 和 useCallback

这两个 hook 都是返回一个缓存的值,当依赖值更新时才会进行更新.

useMemo 返回的值为传入回调的返回值, useCallback 返回值为传入的回调

useCallback(fn, [...deps]) 等同于 useMemo(() => fn,[...deps])

在 useEffect 中我们提到 useEffect 使用外部定义函数时,空数组会被 linter 警告。这种情况时,我们可以使用 useCallback 定义函数,提供给 useEffect 使用

useMemo 可以理解为 Vue 中的 computed.

vivipure commented 2 years ago

useRef

const refContainer = useRef(initialValue);

useRef 是一个特殊的hook, 它返回一个包含 current 属性的对象。

useRef 的值初始化后会永远存在,组件刷新不会更改当前的值,常用于访问 dom

const divEl = useRef(null);
...
<div ref={divEl}></div>

同时 ref的返回值更改后不会触发 组件的刷新,我们可以定义一些不需要组件刷新的数据。

vivipure commented 2 years ago

useImperativeHandle

useImperativeHandle(ref, createHandle, [deps])

useImperativeHandle 用于子组件暴露方法给父组件使用, 同时可以定义依赖项,当依赖项改变后,handle 中暴露的属性也会变化。

useImperativeHandle 需要结合 forwardRef 一起使用

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

useImperativeHandle 主要是为了避免将 ref 透传,即子组件使用父组件定义的ref 绑定dom.

vivipure commented 2 years ago

useLayoutEffect

useLayoutEffect 和 useEffect 的确有相似之处,但是区别也比较大。

区别:

  1. useLayoutEffect 是同步执行, useEffect是异步执行的
  2. useEffect 的执行时机是浏览器完成渲染之后,而 useLayoutEffect 的执行时机是浏览器把内容真正渲染到界面之前。

一些dom渲染的操作可以放在 useLayoutEffect 中执行,避免异步执行造成界面闪烁。

useLayoutEffect 会阻塞页面渲染,所以尽量优先使用 useEffect

在SSR 模式 useLayoutEffect 可能会导致实际渲染的内容和服务端首屏渲染的内容不一致,所以尽量避免在 SSR 模式使用该hook

vivipure commented 2 years ago

useDebug 和 useDeferredValue

useDebug 用于在 React Devtools 将所在hook 作为 label 进行展示

useDeferredValue 用于延迟值的获取,降低优先级

const deferredValue = useDeferredValue(value);

例如我们在使用筛选时,输入变化时会更新列表。如果列表的数据量较多时渲染可能就会阻塞交互,导致输入卡顿。

当我们使用 useDeferredValue 将值进行包裹后,列表渲染的值会有延迟,保证不会阻塞输入的交互。这个和防抖有点像,但是这个hook 更多是在当前组件空闲时进行渲染,在特定的场景还是比较有用的。

vivipure commented 2 years ago

useTransition

const [isPending, startTransition] = useTransition();

useTransition 提供一个状态和一个handler. 我们可以在handle 中执行数据的变化。

当数据 setState 时,isPending 会变为true, 数据异步修改完成后,isPending会变为false。

可以使用 useTransition 处理一些加载的逻辑

function App() {
  const [isPending, startTransition] = useTransition();
  const [count, setCount] = useState(0);

  function handleClick() {
    startTransition(() => {
      setCount(c => c + 1);
    })
  }

  return (
    <div>
      {isPending && <Spinner />}
      <button onClick={handleClick}>{count}</button>
    </div>
  );
}