Skip to content

React 笔记

默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件

Hooks

在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。

Hook 是特殊的函数,只在 React 渲染时有效。

useState()

使用

类似 vue 中的 ref 或者 reactive 变量。但是需要使用特定的 setter 函数改变这个值。

普通的变量发生改变时,并不会响应式的导致视图的变化。

  1. 局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
  2. 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。

要造成响应式的效果,使用 useState Hook:

  • State 变量 用于保存渲染间的数据。
  • State setter 函数 更新变量并触发 React 再次渲染组件。

要使用这个 Hook,需要导入并接收返回的数组对象(这里使用结构接收数组内的元素),其中

  • 第一个元素是 state 本身即响应式数据
  • 第二个元素是 statesetter 函数,调用该函数改变 state 数据的值会触发一次渲染
jsx
import { useState } from 'react';
const [yourdata, setYourdata] = useState(initData)

function clickHandler (e) {
  setYourdata(newData)
}

state 如同一张快照

“正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的

jsx
import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

尽管你调用了三次 setNumber(number + 1),但在 这次渲染 的事件处理函数中 number 会一直是 0,所以你会三次将 state 设置成 1。这就是为什么在你的事件处理函数执行完以后,React 重新渲染的组件中的 number 等于 1 而不是 3。

点击按钮的效果可以描述为如下代码:

jsx
<button onClick={() => {
  setNumber(0 + 1);
  setNumber(0 + 1);
  setNumber(0 + 1);
}}>+3</button>

useContext

这个 api 用于在组件树中传递数据,一般是当需要向深层的组件传递数据(通信)时用到。

使用这个 Hook 需要事先导入。在需要使用这个数据的组件最顶级(组件树最顶层)调用 useContext 来读取和订阅 context.

useContext 返回你向 context 传递的 context value。为了确定 context 值,React 搜索组件树,为这个特定的 context 向上查找最近的 context provider。

若要将 context 传递给 Button,请将其或其父组件之一包装到相应的 context provider,用 value 属性向其传递提供的 context

jsx
import { useContext } from 'react';

function MyPage() {
  return (
    <ThemeContext.Provider value="dark">
      <Form />
    </ThemeContext.Provider>
  );
}

function Form() {
  // ... 在内部渲染 buttons ...
}

function Button() {
  const theme = useContext(ThemeContext);
  // ...
}

TIP

这里 Button 通过 useContext 引用 ThemeContext, 那么 React 向上搜索组件树中最近的 Provider 提供的 value.

useReducer

useReducer 允许你将状态更新逻辑与事件处理程序分离开来,它将组件的所有状态更新逻辑整合到一个外部函数中,这个函数叫作 reducer.

参数

  • reducer:用于更新 state 的纯函数。参数为 stateaction,返回值是更新后的 statestate 与 action 可以是任意合法值。
  • initialArg:用于初始化 state 的任意值。初始值的计算逻辑取决于接下来的 init 参数。
  • 可选参数 init:用于计算初始值的函数。如果存在,使用 init(initialArg) 的执行结果作为初始值,否则使用 initialArg。

示例

jsx
/**
 * @param {any} state 需要存储的状态数据
 * @param {Object} action 一个具有 type 属性的对象
 * @returns [state - 响应式数据,dispatch - 更新 state 的方法]
 */
function reducer (state, action) {
  switch (action.type) {
    case "increase":
      return state + 1
    case "decrease":
      return state - 1
    default:
      return new Error()
  }
}

export default function App() {
  // state 和 更新 state 的方法
  const [state, dispatch] = useReducer(reducer, 100)
  const handleIncrease = () => {
    dispatch({type: "increase"})
  }
  const handleDerease = () => {
    dispatch({type: "decrease"})
  }
  return (
    <div className="App">
      <button onClick={handleDerease}>-</button>
      <span>{state}</span>
      <button onClick={handleIncrease}>+</button>
    </div >
  );
}

useRef

使用

useRef 是一个 React Hook,它能帮助引用一个不需要渲染的值。

参数 initialValue - ref 对象的 current 属性的初始值。可以是任意类型的值。这个参数在首次渲染后被忽略。

返回一个只有一个属性 current 的对象,初始值为传递的 initialValue。如果将 ref 对象作为一个 JSX 节点的 ref 属性传递给 React,React 将为它设置 current 属性。

可以使用 ref 存储数据或者操作 DOM 对象:

jsx
import { useRef } from 'react';

export default function Form() {
  const inputRef = useRef(null);

  function handleClick() {
    inputRef.current.focus();
  }

  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>
        聚焦输入框
      </button>
    </>
  );
}

特点

主要是和 useState Hook 进行对比。

  • 可以在重新渲染之间 存储信息(普通对象存储的值每次渲染都会重置)
  • 改变它 不会触发重新渲染(状态变量会触发重新渲染)
  • 对于组件的每个副本而言,这些信息都是本地的(外部变量则是共享的)

当你希望组件“记住”某些信息,但又不想让这些信息触发新的渲染时,可以使用 ref,所以 ref 是存储一些不影响视图的输出信息的完美选择。

useImperative

useImperativeHandle 是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。

以下是一个父组件引用子组件方法的示例。

jsx
const Child = forwardRef(function (props, ref) {
  useImperativeHandle(ref, () => ({
    // 暴露给父组件的方法
    myFn: () => {
      console.log("Fn invoked.")
    },
  }))
  return <div>子组件</div>
})
function App() {
  const Childref = useRef(null)

  return (
    <div className="App">
      <Child ref={Childref}></Child>
      <button onClick={() => {Childref.current.myFn()}}>
        调用子组件方法
      </button>
    </div>
  )
}

useEffect

参数

setup:处理 Effect 的函数。setup 函数选择性返回一个 清理(cleanup) 函数。

当组件被添加到 DOM 的时候,React 将运行 setup 函数。在每次依赖项变更重新渲染后,React 将首先使用旧值运行 cleanup 函数(如果你提供了该函数),然后使用新值运行 setup 函数。在组件从 DOM 中移除后,React 将最后一次运行 cleanup 函数。

可选 dependencies:setup 代码中引用的所有响应式值的列表。

响应式值包括 props、state 以及所有直接在组件内部声明的变量和函数。依赖项列表的元素数量必须是固定的,并且必须像 [dep1, dep2, dep3] 这样内联编写。React 将使用 Object.is 来比较每个依赖项和它先前的值。如果省略此参数,则在每次重新渲染组件之后,将重新运行 Effect 函数。

jsx
function App() {
  const [count, setCount] = useState(10)
  const [inputValue, setInputValue] = useState(1000)

  useEffect(() => console.log('App 组件加载了 || count 发生变化了'), [count])

  return (
    <div className="App">
      <div>count: {count}</div>
      <button onClick={() => setCount(count+1)}>change count</button>
      <input type="text" value={inputValue} onInput={(e) => setInputValue(e.target.value)} />
      <Child value={inputValue}></Child>
    </div >
  );
}

useMemo

useMemo 是一个 React Hook,它在每次重新渲染的时候能够缓存计算的结果。类似 Vue 的 computed 计算属性。

参数

calculateValue:要缓存计算值的函数。它应该是一个没有任何参数的纯函数,并且可以返回任意类型。

React 将会在首次渲染时调用该函数;在之后的渲染中,如果 dependencies 没有发生变化,React 将直接返回相同值。否则,将会再次调用 calculateValue 并返回最新结果,然后缓存该结果以便下次重复使用。

dependencies:所有在 calculateValue 函数中使用的响应式变量组成的数组。

响应式变量包括 props、state 和所有你直接在组件中定义的变量和函数。如果你在代码检查工具中 配置了 React,它将会确保每一个响应式数据都被正确地定义为依赖项。依赖项数组的长度必须是固定的并且必须写成 [dep1, dep2, dep3] 这种形式。React 使用 Object.is 将每个依赖项与其之前的值进行比较。

观察以下例子

jsx
function Child ({ value }) {
  const result = useMemo(() => {
    // 假设这里是某种开销很大的复杂运算
    return value * 2
  }, [value])
  return (
    <div>
      value * 2 = {result}
    </div>
  )
}

function App() {
  // 这是一个与子组件不相关的数据 count
  const [count, setCount] = useState(10)
  const [inputValue, setInputValue] = useState(1000)
  return (
    <div className="App">
      <div>count: {count}</div>
      <button onClick={() => setCount(count+1)}>change count</button>
      <input type="text" value={inputValue} onInput={(e) => setInputValue(e.target.value)} />
      <Child value={inputValue}></Child>
    </div >
  )
}

例子中,如果计算 result 的函数是一个普通的函数或者只是普通的代码运算,那么当父组件中的 count 改变触发重新渲染时,Child 函数被重新执行,计算 result 值的过程将会被重新运行,即使计算 result 值并没有变化,也并不使用 count

当将计算 result 的过程包裹在 useMemo 内并传入依赖数组(计算 result 需要的数据)后,只要 value 没有改变,都不会重新计算 result 值,而是直接读缓存。

useCallback

useCallback 是一个允许你在多次渲染中缓存函数的 React Hook. 类似于 useMemo 缓存数据,useCallback 缓存的是一个函数。

默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件。可以将组件包裹在 memo() 中,重新渲染时,如果 props 和上一次渲染时相同,该组件将跳过重新渲染。

如果传入的 props 是父组件的一个函数,每次父组件重新渲染,这个函数都会重新声明和初始化(组件函数都会重新执行一次)这意味着每一次重新渲染,父组件都传给子组件的 props 函数和上一次渲染的 props 函数不是同一个

使用 useCallback 处理这个函数即可让其只在依赖项发生变化时重新生成,也就是说在不同轮次渲染之间缓存这个函数。

jsx
const ShippingForm = memo(function ShippingForm({ onSubmit }) {
  // ...
});

function ProductPage({ productId, referrer, theme }) {
  // 在多次渲染中缓存函数
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]); // 只要这些依赖没有改变

  return (
    <div className={theme}>
      {/* ShippingForm 就会收到同样的 props 并且跳过重新渲染 */}
      <ShippingForm onSubmit={handleSubmit} />
    </div>
  );
}

Released under the GNU General Public License v3.0.