React 笔记
默认情况下,当一个组件重新渲染时, React 将递归渲染它的所有子组件
Hooks
在 React 中,useState
以及任何其他以“use”开头的函数都被称为 Hook。
Hook 是特殊的函数,只在 React 渲染时有效。
useState()
使用
类似 vue 中的 ref 或者 reactive 变量。但是需要使用特定的 setter 函数改变这个值。
普通的变量发生改变时,并不会响应式的导致视图的变化。
- 局部变量无法在多次渲染中持久保存。 当 React 再次渲染这个组件时,它会从头开始渲染——不会考虑之前对局部变量的任何更改。
- 更改局部变量不会触发渲染。 React 没有意识到它需要使用新数据再次渲染组件。
要造成响应式的效果,使用 useState
Hook:
- State 变量 用于保存渲染间的数据。
- State setter 函数 更新变量并触发 React 再次渲染组件。
要使用这个 Hook,需要导入并接收返回的数组对象(这里使用结构接收数组内的元素),其中
- 第一个元素是
state
本身即响应式数据 - 第二个元素是
state
的setter
函数,调用该函数改变state
数据的值会触发一次渲染
import { useState } from 'react';
const [yourdata, setYourdata] = useState(initData)
function clickHandler (e) {
setYourdata(newData)
}
state 如同一张快照
“正在渲染” 就意味着 React 正在调用你的组件——一个函数。你从该函数返回的 JSX 就像是 UI 的一张及时的快照。它的 props、事件处理函数和内部变量都是 根据当前渲染时的 state 被计算出来的。
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。
点击按钮的效果可以描述为如下代码:
<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
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
的纯函数。参数为state
和action
,返回值是更新后的state
。state
与 action 可以是任意合法值。initialArg
:用于初始化state
的任意值。初始值的计算逻辑取决于接下来的init
参数。- 可选参数
init
:用于计算初始值的函数。如果存在,使用init(initialArg)
的执行结果作为初始值,否则使用 initialArg。
示例
/**
* @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 对象:
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 暴露出来的句柄。
以下是一个父组件引用子组件方法的示例。
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 函数。
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 将每个依赖项与其之前的值进行比较。
观察以下例子
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
处理这个函数即可让其只在依赖项发生变化时重新生成,也就是说在不同轮次渲染之间缓存这个函数。
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>
);
}