React & 解释常见的 hooks: useState / useRef / useContext / useReducer

前言

如果对 re-render 概念还不清楚,建议先看 React & 理解 re-render 的作用、概念,并提供详细的例子解释 再回头看本文。
如果对 React 基础语法还不熟练,建议先看 React & JSX 日常用法与基本原则 再回头看本文。

useState

useState 可以用来声明响应式数据。

使用案例:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useState } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {
  const [name, setName] = useState('Jack')
  return <>
    <div>My name is {name}</div>
    <button onClick={() => setName('John')}>Change my name</button>
  </>
}
root.render(
  <App />
);

效果:

注意,state 更新采用异步执行 re-render,因此控制台打印的值仍然是旧的。

若希望能够在 state 变更后马上拿到最新的值,有两种方案:

  1. 使用临时变量存储要变更的值:
js 复制代码
// ...
const onChangeName = () => {
  const tempName = 'John'
   setName(tempName)
   console.log('My current name is', tempName)
 }
// ...
  1. 使用 useRef,下面会讲。

useRef

useRef 也是声明响应式数据的一种方式,与 useState 不同的是,它可以不受 re-render 的约束,在更新数据后能立刻访问最新的值。

使用案例:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useRef } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const App = () => {
  const name = useRef('Jack')
  const onChangeName = () => {
    name.current = 'John'
    console.log('My current name is', name.current)
  }
  return <>
    <div>My name is {name.current}</div>
    <button onClick={onChangeName}>Change my name</button>
  </>
}
root.render(
  <App />
);

效果:

可以看出 useRef 与 useState 是两种相反的结果,控制台为最新,视图为旧。

在不强调视图的情况可以考虑用 useRef ,比如异步回调获取最新数据的场景。

useReducer

useReducer 是 useState 的进阶版,当 useState 操作逻辑变得复杂时,可以将它们提升为 useReducer 的书写方式 ,提高可读、维护性。

下面是 useEffect VS useReducer 相同功能的对比图:

useReducer 通过 switch 声明式结构,我们能一眼就能看出每个 type 都做了哪些事。

从对比图看,虽然没能展现 useReducer 的优势,但我们只要理解它的逻辑处理结构就行了,

多一种选择,何乐而不为呢。

在 useState 处理少量逻辑的情况下优先 useState,反之 useReducer 。

案例代码:

js 复制代码
import ReactDOM from 'react-dom/client';
import { useReducer } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));

const todoReducer = (state, action) => {
  switch (action.type) {
    case 'add':
      return {
        ...state,
        todos: [
          ...state.todos,
          Math.floor(Math.random() * 1000)
        ]
      }
    case 'remove':
      return {
        ...state,
        todos: state.todos.splice(state.index)
      }
    default:
      return state
  }
}
const App = () => {
  const [state, dispatch] = useReducer(todoReducer, { todos: [] })
  return <>
    <header>Todos</header>
    <button onClick={() => dispatch({ type: 'add' })}>Add todo</button>
    <button onClick={() => dispatch({ type: 'remove' })}>Remove todo</button>
    <ul>
      {state.todos.map((todo) => (
        <li key={todo}>
          {todo}
        </li>
      ))}
    </ul>
  </>
}
root.render(
  <App />
);

参数解释:useReducer(reducer, initialArg, init?)

  • 第一个参数是一个 handler,即要处理数据的函数。
  • 第二个参数是默认值,和 useState(xx) 的第一个参数一样。
  • 第三个参数是一个 useMemo 回调函数(可选项),它可以缓存第二个参数的数据,避免 re-render 重复定义默认值。

useReducer 返回的 state / dispatch 表示:

  • state 获取我们的数据。
  • dispatch 触发 handler 函数

提示:掌握了 useReducer 等于学会了 react-redux 框架,它与 useReducer 的区别仅仅多了一层全局缓存的含义,对 react-redux 感兴趣的可参考:
React & 用一个简单案例体验一遍 React-dom & React-router & React-redux 全家桶

useContext

useContext 可以解决多层组件传递 props 数据的问题,如果你用过 Vue 的 provide/inject 函数,那你也就会了,它们俩的作用&概念是一致的。

案例代码:

js 复制代码
import ReactDOM from 'react-dom/client';
import { createContext, useContext, useState } from 'react';
const root = ReactDOM.createRoot(document.getElementById("root"));
const Profile = () => {
  const userInfo = useContext(UserInfoContext)
  return <>
    <label>Name: {userInfo.name}</label><br />
    <label>Age: {userInfo.age}</label><br />
    <label>Hobbies: 
      {userInfo.hobbies.map(hobby => <span key={hobby}>{hobby} </span>)}
    </label>
  </>
}
const ShoppingCart = () => {
  const userInfo = useContext(UserInfoContext)
  return <>
    <footer>
      Shopping Cart:
    </footer>
    <ul>
      {userInfo.carts.map((cart) => <li key={cart}>{cart}</li>)}
    </ul>
    Total num: {userInfo.carts.length }
  </>
}

const UserInfoContext = createContext(null)
const App = () => {
  const [userInfo] = useState({
    name: 'Jack',
    age: 30,
    hobbies: ['Running', 'Painting'],
    carts: ['Dog toy', 'Cup']
  })
  return <>
    <UserInfoContext.Provider value={userInfo}>
      <Profile/>
      <ShoppingCart/>
    </UserInfoContext.Provider>
  </>
}
root.render(
  <App />
);

效果:

useMemo / useCallback

useMemo 可以缓存非响应式数据,避免 re-render 的问题。

useCallback 可以缓存函数,避免 re-render 的问题。

对于函数缓存,虽然 useMemo 也能实现,但还是得额外嵌套一层函数,因此官方建议使用 useCallback 。

俩钩子的用法在 React & 理解 re-render 的作用、概念,并提供详细的例子解释 都有详细的解释,这里不再赘述。


完!

相关推荐
总要学点什么21 分钟前
第105篇:三维特效-流动图片墙体效果——让箭头沿墙长滚,它俩竟是同一份代码
开发语言·javascript·ecmascript·gis·webgl·材质·cesium
Agent世界2 小时前
用 CDP 做发布自动化:四道机器闸门与产物验收
前端·javascript
FungLeo2 小时前
成为全栈·Vue 管理后台篇·编辑器图片上传:从文件选择到文章附件关联
前端·vue.js·编辑器·markdown·文件上传·formdata·附件管理
总要学点什么2 小时前
第104篇:三维特效-流动墙体效果——让贴图沿墙流动,它却只肯走一条对角线
开发语言·javascript·ecmascript·gis·webgl·材质·cesium
某哈压力大2 小时前
SSH 突然 Connection reset?不是密钥坏了,是 OpenSSL 把 sshd 干趴了
前端·node.js·命令行
程序员大阳2 小时前
副队长ES6+教程(3)--解构赋值
前端·javascript·es6·赋值·解构
神膘护体小月半2 小时前
css - 元素背景图由 hover 触发,初次加载时闪动的解决办法
前端·javascript·css
程序员大阳3 小时前
副队长ES6+教程(2)--let 与 const
前端·javascript·es6·const·let·var
deli0073 小时前
写满 200GB 后 SSD 为什么掉到 95MB/s?SLC 缓存模型实测
前端
曦樂~4 小时前
【Web Development】03JavaScript 基础和事件
开发语言·前端·javascript