Hooks
- Hooks ——以 use 开头的函数——只能在组件或自定义 Hook 的最顶层调用。 你不能在条件语句、循环语句或其他嵌套函数内调用 Hook。Hook 是函数,但将它们视为关于组件需求的无条件声明会很有帮助。在组件顶部 “use” React 特性,类似于在文件顶部“导入”模块。
- 在 React 中,useState 以及任何其他以“use”开头的函数都被称为 Hook。
- Hook 是特殊的函数,只在 React 渲染时有效(我们将在下一节详细介绍)。它们能让你 “hook” 到不同的 React 特性中去。
-
useState的使用
每次你的组件渲染时,useState 都会给你一个包含两个值的数组:
- state 变量 (index) 会保存上次渲染的值。
- state setter 函数 (setIndex) 可以更新 state 变量并触发 React 重新渲染组件。
以下是实际发生的情况:
const [index, setIndex] = useState(0);
- 组件进行第一次渲染。 因为你将 0 作为 index 的初始值传递给 useState,它将返回 [0, setIndex]。 React 记住 0 是最新的 state 值。
- 你更新了 state。当用户点击按钮时,它会调用 setIndex(index + 1)。 index 是 0,所以它是 setIndex(1)。这告诉 React 现在记住 index 是 1 并触发下一次渲染。
- 组件进行第二次渲染。React 仍然看到 useState(0),但是因为 React 记住 了你将 index 设置为了 1,它将返回 [1, setIndex]。
- 以此类推!
-
重点:组件进行第二次渲染。React 仍然看到 useState(0),但是因为 React 记住 了你将 index 设置为了 1,它将返回 [1, setIndex]。
-
自己实现useState
-
let componentHooks = []; let currentHookIndex = 0; // useState 在 React 中是如何工作的(简化版) function useState(initialState) { let pair = componentHooks[currentHookIndex]; if (pair) { // 这不是第一次渲染 // 所以 state pair 已经存在 // 将其返回并为下一次 hook 的调用做准备 currentHookIndex++; return pair; } // 这是我们第一次进行渲染 // 所以新建一个 state pair 然后存储它 pair = [initialState, setState]; function setState(nextState) { // 当用户发起 state 的变更, // 把新的值放入 pair 中 pair[0] = nextState; updateDOM(); } // 存储这个 pair 用于将来的渲染 // 并且为下一次 hook 的调用做准备 componentHooks[currentHookIndex] = pair; currentHookIndex++; return pair; } function Gallery() { // 每次调用 useState() 都会得到新的 pair const [index, setIndex] = useState(0); const [showMore, setShowMore] = useState(false); function handleNextClick() { setIndex(index + 1); } function handleMoreClick() { setShowMore(!showMore); } let sculpture = sculptureList[index]; // 这个例子没有使用 React,所以 // 返回一个对象而不是 JSX return { onNextClick: handleNextClick, onMoreClick: handleMoreClick, header: `${sculpture.name} by ${sculpture.artist}`, counter: `${index + 1} of ${sculptureList.length}`, more: `${showMore ? 'Hide' : 'Show'} details`, description: showMore ? sculpture.description : null, imageSrc: sculpture.url, imageAlt: sculpture.alt }; } function updateDOM() { // 在渲染组件之前 // 重置当前 Hook 的下标 currentHookIndex = 0; let output = Gallery(); // 更新 DOM 以匹配输出结果 // 这部分工作由 React 为你完成 nextButton.onclick = output.onNextClick; header.textContent = output.header; moreButton.onclick = output.onMoreClick; moreButton.textContent = output.more; image.src = output.imageSrc; image.alt = output.imageAlt; if (output.description !== null) { description.textContent = output.description; description.style.display = ''; } else { description.style.display = 'none'; } } let nextButton = document.getElementById('nextButton'); let header = document.getElementById('header'); let moreButton = document.getElementById('moreButton'); let description = document.getElementById('description'); let image = document.getElementById('image'); let sculptureList = [{ name: 'Homenaje a la Neurocirugía', artist: 'Marta Colvin Andrade', description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.', url: 'https://i.imgur.com/Mx7dA2Y.jpg', alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.' }, { name: 'Floralis Genérica', artist: 'Eduardo Catalano', description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.', url: 'https://i.imgur.com/ZF6s192m.jpg', alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.' }, { name: 'Eternal Presence', artist: 'John Woodrow Wilson', description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."', url: 'https://i.imgur.com/aTtVpES.jpg', alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.' }, { name: 'Moai', artist: 'Unknown Artist', description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.', url: 'https://i.imgur.com/RCwLEoQm.jpg', alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.' }, { name: 'Blue Nana', artist: 'Niki de Saint Phalle', description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.', url: 'https://i.imgur.com/Sd1AgUOm.jpg', alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.' }, { name: 'Ultimate Form', artist: 'Barbara Hepworth', description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.', url: 'https://i.imgur.com/2heNQDcm.jpg', alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.' }, { name: 'Cavaliere', artist: 'Lamidi Olonade Fakeye', description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.", url: 'https://i.imgur.com/wIdGuZwm.png', alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.' }, { name: 'Big Bellies', artist: 'Alina Szapocznikow', description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.", url: 'https://i.imgur.com/AlHTAdDm.jpg', alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.' }, { name: 'Terracotta Army', artist: 'Unknown Artist', description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.', url: 'https://i.imgur.com/HMFmH6m.jpg', alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.' }, { name: 'Lunar Landscape', artist: 'Louise Nevelson', description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.', url: 'https://i.imgur.com/rN7hY6om.jpg', alt: 'A black matte sculpture where the individual elements are initially indistinguishable.' }, { name: 'Aureole', artist: 'Ranjani Shettar', description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."', url: 'https://i.imgur.com/okTpbHhm.jpg', alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.' }, { name: 'Hippos', artist: 'Taipei Zoo', description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.', url: 'https://i.imgur.com/6o5Vuyu.jpg', alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.' }]; // 使 UI 匹配当前 state updateDOM();
-
-
浓缩精华
-
let stateArr = []; let currentHookIndex = 0; function useState(initialState) { let pair = stateArr[currentHookIndex]; if(pair) { currentHookIndex++; return pair; } function setState(nextState) { pair[0] = nextState; // 触发渲染 updateDom(); } pair = [initialState, setState] stateArr[currentHookIndex] = pair; currentHookIndex++; return pair; }
-
渲染
-
更新组件的状态会自动将一次渲染送入队列。(你可以把这种情况想象成餐厅客人在第一次下单之后又点了茶、点心和各种东西,具体取决于他们的胃口。)
-
状态更新...
-
...触发...
-
...渲染!
-
-
有两种原因会导致组件的渲染:
- 组件的 初次渲染。
- 组件(或者其祖先之一)的 状态发生了改变。
-
在 “严格模式” 下开发时,React 会调用每个组件的函数两次,这可以帮助发现由不纯函数引起的错误。
- 这种模式只在开发环境中启用,生产环境不会执行两次渲染。
-
React 仅在渲染之间存在差异时才会更改 DOM 节点。
- react可能会渲染子组件,但子组件没变化时不会更新DOM。
-
在一个 React 应用中一次屏幕更新都会发生以下三个步骤:
- 触发
- 渲染
- 提交
-
当 React 重新渲染一个组件时:
- React 会再次调用你的函数(这个函数就是react 组件函数)
- 函数会返回新的 JSX 快照
- React 会更新界面以匹配返回的快照
State
-
一个 state 变量的值永远不会在一次渲染的内部发生变化, 即使其事件处理函数的代码是异步的。
- 一次渲染就是一张快照,里面的state是固定不变的。
-
设置 state 不会更改现有渲染中的变量,但会请求一次新的渲染。
-
比如连续执行多次同一个setNumber,并不会使得number的值+3
-
export default function Counter() { const [number, setNumber] = useState(0); return ( <> <h1>{number}</h1> <button onClick={() => { setNumber(n => n + 1); setNumber(n => n + 1); setNumber(n => n + 1); }}>+3</button> </> ) }
-
-
要在一个事件中多次更新某些 state,你可以使用 setNumber(n => n + 1) 更新函数。
-
所以上面简单版的useState实现可以改成:
-
let stateArr = []; let currentHookIndex = 0; function useState(initialState) { let pair = stateArr[currentHookIndex]; if(pair) { currentHookIndex++; return pair; } function setState(nextState) { if(typeof nextState === 'function') { pair[0] = nextState(pair[0]); }else { pair[0] = nextState; } // 触发渲染 updateDom(); } pair = [initialState, setState] stateArr[currentHookIndex] = pair; currentHookIndex++; return pair; }
-
-
状态管理
-
useReducer。
-
当状态较为简单,只涉及单个状态值的更新时,我倾向于使用 useState。比如,管理一个简单的开关状态、输入框的值等。
import React, { useState } from 'react'; function Example() { const [count, setCount] = useState(0); // 使用 useState 管理简单的状态 // ... } -
当状态较为复杂,涉及多个子状态或者需要进行多个状态的联动更新时,选择使用 useReducer。比如,管理一个包含多个子状态的复杂状态对象,或者需要进行复杂的状态逻辑处理。
jsx
import React, { useReducer } from 'react'; const initialState = { count: 0, isOn: false }; function reducer(state, action) { switch (action.type) { case 'increment': return { ...state, count: state.count + 1 }; case 'toggle': return { ...state, isOn: !state.isOn }; default: throw new Error(); } } function Example() { const [state, dispatch] = useReducer(reducer, initialState); // 使用 useReducer 管理复杂状态 // ... }总的来说,根据状态的复杂程度和更新逻辑来选择使用 useState 或 useReducer,以便更好地组织和管理组件的状态,提高代码的可维护性和可预测性。
-
自己实现useReducer
-
import { useState } from 'react'; export function useReducer(reducer, initialState) { const [state, setState] = useState(initialState); // function dispatch(action) { // const nextState = reducer(state, action); // setState(nextState); // } // 是在setState里调用函数,而不是先调用函数,再调用setState。 // 根据上面的内容,想一想为什么? function dispatch(action) { setState((s) => reducer(s, action)); } return [state, dispatch]; }
-
-
-
构建State的原则
-
合并关联的 state。如果你总是同时更新两个或更多的 state 变量,请考虑将它们合并为一个单独的 state 变量。
-
避免互相矛盾的 state。当 state 结构中存在多个相互矛盾或“不一致”的 state 时,你就可能为此会留下隐患。应尽量避免这种情况。
-
避免冗余的 state。如果你能在渲染期间从组件的 props 或其现有的 state 变量中计算出一些信息,则不应将这些信息放入该组件的 state 中。
-
不要在 state 中镜像 props。想要 忽略特定 props 属性的所有更新时,将 props “镜像”到 state 才有意义。按照惯例,prop 名称以 initial 或 default 开头,以阐明该 prop 的新值将被忽略:
-
function Message({ initialColor }) { // 这个 `color` state 变量用于保存 `initialColor` 的 **初始值**。 // 对于 `initialColor` 属性的进一步更改将被忽略。 const [color, setColor] = useState(initialColor); ... }
-
-
-
避免重复的 state。当同一数据在多个 state 变量之间或在多个嵌套对象中重复时,这会很难保持它们同步。应尽可能减少重复。
-
避免深度嵌套的 state。深度分层的 state 更新起来不是很方便。如果可能的话,最好以扁平化方式构建 state。
-
-
对state进行保留和重置
- 相同位置的不同组件会使 state 重置
- 相同位置的相同组件会使得 state 被保留下来
- 这个位置,指的是组件在 UI 树中的位置,而不是在 JSX 中的位置
- React组件里的 key 不是全局唯一的。它们只能指定 父组件内部 的顺序。
-
-
你可以将 reducer 与 context 相结合,让任何组件读取和更新它的状态。
-
为子组件提供 state 和 dispatch 函数:
- 创建两个 context (一个用于 state,一个用于 dispatch 函数)。
- 让组件的 context 使用 reducer。
- 使用组件中需要读取的 context。
-
你可以通过将所有传递信息的代码移动到单个文件中来进一步整理组件。
- 你可以导出一个像 TasksProvider 可以提供 context 的组件。
- 你也可以导出像 useTasks 和 useTasksDispatch 这样的自定义 Hook。
-
你可以在你的应用程序中大量使用 context 和 reducer 的组合。
-
useRef
-
使用ref引用值
-
ref state useRef(initialValue)返回 { current: initialValue } useState(initialValue) 返回 state 变量的当前值和一个 state 设置函数 ( [value, setValue]) 更改时不会触发重新渲染 更改时触发重新渲染。 可变 —— 你可以在渲染过程之外修改和更新 current 的值。 “不可变” —— 你必须使用 state 设置函数来修改 state 变量,从而排队重新渲染。 你不应在渲染期间读取(或写入) current 值。 你可以随时读取 state。但是,每次渲染都有自己不变的 state 快照。 更改ref,会立即生效 更改state,在渲染后才会生效 -
自己实现useRef
-
// React 内部 function useRef(initialValue) { const [ref, unused] = useState({ current: initialValue }); return ref; }
-
-
何时使用ref
- 存储 timeout ID
- 存储和操作 DOM 元素,我们将在 下一页 中介绍
- 存储不需要被用来计算 JSX 的其他对象。
- 如果你的组件需要存储一些值,但不影响渲染逻辑,请选择 ref。
-
-
使用ref操作DOM
-
获取指向节点的ref
-
要访问由 React 管理的 DOM 节点,首先,引入 useRef Hook:
import { useRef } from 'react';
然后,在你的组件中使用它声明一个 ref:
const myRef = useRef(null);
最后,将 ref 作为 ref 属性值传递给想要获取的 DOM 节点的 JSX 标签:
-
-
-
当你将 ref 放在像 这样输出浏览器元素的内置组件上时,React 会将该 ref 的 current 属性设置为相应的 DOM 节点(例如浏览器中实际的 )。
-
但是,如果你尝试将 ref 放在 你自己的 React组件上,例如 ,默认情况下你会得到 null。
-
想要 暴露其 DOM 节点的组件必须选择该行为。一个组件可以指定将它的 ref “转发”给一个子组件。下面是 MyInput 如何使用 forwardRef API:
-
const MyInput = forwardRef((props, ref) => { return <input {...props} ref={ref} />; }); // 或者 export default forwardRef( function SearchInput(props, ref) { return ( <input ref={ref} placeholder="找什么呢?" /> ); } );
-
-
它是这样工作的:
- 告诉 React 将对应的 DOM 节点放入 inputRef.current 中。但是,这取决于 MyInput 组件是否允许这种行为, 默认情况下是不允许的。
- MyInput 组件是使用 forwardRef 声明的。 这让从上面接收的 inputRef 作为第二个参数 ref 传入组件,第一个参数是 props 。
- MyInput 组件将自己接收到的 ref 传递给它内部的 。
-
-
使用命令句柄暴露一部分 API
-
useImperativeHandle,内部用useRef将需要的api暴露出来,交给外部的ref使用。
- 类似于一个类里只把export的api暴露给外部使用
-
const MyInput = forwardRef((props, ref) => { const realInputRef = useRef(null); useImperativeHandle(ref, () => ({ // 只暴露 focus,没有别的 focus() { realInputRef.current.focus(); }, })); return <input {...props} ref={realInputRef} />; });
-
-
flushSync强制同步更新
-
useEffect
-
不要在useEffect里使用useState(在没有依赖数组的时候)
-
const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }); // 以上代码,没有依赖数组,每次渲染结束都会执行 Effect;而更新 state 会触发重新渲染。但是新一轮渲染时又会再次执行 Effect,然后 Effect 再次更新 state……如此周而复始,从而陷入死循环。 // 下面代码就不会循环渲染 const [count, setCount] = useState(0); useEffect(() => { setCount(count + 1); }, []);
-
-
React 使用 Object.is 比较useEffect依赖项的值
- 如果依赖项是Object或者Function,相当于每次都变。
- 尽可能避免将对象和函数作为 Effect 的依赖。所以,尝试将它们移到组件外部、Effect 内部,或从中提取原始值。
-
依赖数组不同时的行为
-
useEffect(() => { // 这里的代码会在每次渲染后执行 }); useEffect(() => { // 这里的代码只会在组件挂载后执行 }, []); useEffect(() => { //这里的代码只会在每次渲染后,并且 a 或 b 的值与上次渲染不一致时执行 }, [a, b]);
-
-
- useEffect在开发模式下会执行两次
-
useEffect中关于网络请求的常见用法,用到了清理函数变量ignore
-
import React, { useState, useEffect } from 'react'; function Example() { const [data, setData] = useState(null); useEffect(() => { let ignore = false; async function fetchData() { const response = await fetch('https://api.example.com/data'); const data = await response.json(); if (!ignore) setData(data); } fetchData(); return () => { ignore = true; }; }, []); // 依赖数组为空,所以这个 effect 只会在组件挂载后运行一次 return ( <div> {/* render data */} </div> ); }
-
仅开发环境下的行为
-
在 严格模式 下,React 在每次挂载组件后都会重新挂载组件(但是组件的 state 与 创建的 DOM 都会被保留)。它可以帮助你找出需要添加清理函数的 Effect,以及早暴露出像条件竞争那样的问题。此外,每当你在开发环境中保存更新代码文件时,React 也会重新挂载 Effect,不过这两种行为都仅限于开发环境。
-
- 测量某一段代码的执行时间,如果总耗时达到了一定量级(比方说 1ms 或更多),那么把计算结果记忆(memoize)起来可能是有意义的。
- 这时候用useMemo就有价值了。
- useMemo 不会让 第一次 渲染变快。它只是帮助你跳过不必要的更新。
你可能不需要Effect
-
-
export default function ProfilePage({ userId }) { const [comment, setComment] = useState(''); // 🔴 避免:当 prop 变化时,在 Effect 中重置 state useEffect(() => { setComment(''); }, [userId]); // ... } -
// 将props作为组件的key值 export default function ProfilePage({ userId }) { return ( <Profile userId={userId} key={userId} /> ); } function Profile({ userId }) { // ✅ 当 key 变化时,该组件内的 comment 或其他 state 会自动被重置 const [comment, setComment] = useState(''); // ... }
-
-
场景2:在props变化时,部分更新state的内容
-
function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selection, setSelection] = useState(null); // 🔴 避免:当 prop 变化时,在 Effect 中调整 state useEffect(() => { setSelection(null); }, [items]); // ... } -
// 根据props的变化直接变化 function List({ items }) { const [isReverse, setIsReverse] = useState(false); const [selectedId, setSelectedId] = useState(null); // ✅ 非常好:在渲染期间计算所需内容 const selection = items.find(item => item.id === selectedId) ?? null; // ... }
-
-
场景3:将多个事件处理中的函数放到useEffect里共享逻辑(我自己就经常这么写)
-
假设你有一个产品页面,上面有两个按钮(购买和付款),都可以让你购买该产品。当用户将产品添加进购物车时,你想显示一个通知。在两个按钮的 click 事件处理函数中都调用 showNotification() 感觉有点重复,所以你可能想把这个逻辑放在一个 Effect 中:
-
function ProductPage({ product, addToCart }) { // 🔴 避免:在 Effect 中处理属于事件特定的逻辑 useEffect(() => { if (product.isInCart) { showNotification(`已添加 ${product.name} 进购物车!`); } }, [product]); function handleBuyClick() { addToCart(product); } function handleCheckoutClick() { addToCart(product); navigateTo('/checkout'); } // ... } -
function ProductPage({ product, addToCart }) { // ✅ 非常好:事件特定的逻辑在事件处理函数中处理 function buyProduct() { addToCart(product); showNotification(`已添加 ${product.name} 进购物车!`); } function handleBuyClick() { buyProduct(); } function handleCheckoutClick() { buyProduct(); navigateTo('/checkout'); } // ... } -
当你不确定某些代码应该放在 Effect 中还是事件处理函数中时,先自问 为什么 要执行这些代码。Effect 只用来执行那些显示给用户时组件 需要执行 的代码
- 也就是,不是执行某些操作(如点击、滑动)的时候,组件需要有类似事件处理函数的逻辑时,用useEffect。
- 比如,页面初始化后请求数据,就像页面初始化后点击了一个按钮
-
-
场景4:发送POST请求
- 类似于场景3,放到具体的事件里执行
-
场景5:初始化应用,只执行一次
-
// 在生产环境下没有问题,但是在严格模式下会执行两次。 // (看你如何理解副作用了) function App() { // 🔴 避免:把只需要执行一次的逻辑放在 Effect 中 useEffect(() => { loadDataFromLocalStorage(); checkAuthToken(); }, []); // ... } -
let didInit = false; function App() { useEffect(() => { if (!didInit) { didInit = true; // ✅ 只在每次应用加载时执行一次 loadDataFromLocalStorage(); checkAuthToken(); } }, []); // ... }
-
-
场景6:将数据传递给父组件
-
不要子->父,尽量保持数据流的可预测性,父->子
function Parent() { const [data, setData] = useState(null); // ... return <Child onFetched={setData} />; } function Child({ onFetched }) { const data = useSomeAPI(); // 🔴 避免:在 Effect 中传递数据给父组件 useEffect(() => { if (data) { onFetched(data); } }, [onFetched, data]); // ... } -
function Parent() { const data = useSomeAPI(); // ... // ✅ 非常好:向子组件传递数据 return <Child data={data} />; } function Child({ data }) { // ... }
-
-
场景7:订阅外部store
- 不要用useEffect
- 用useSyncExternalStore
- zh-hans.react.dev/learn/you-m…
Effect的生命周期
-
避免将对象和函数作为依赖项。如果在渲染过程中创建对象和函数,然后在 Effect 中读取它们,它们将在每次渲染时都不同。这将导致 Effect 每次都重新同步。
-
Effect的生命周期和它的组件的生命周期不一致
- 每个 Effect 与周围组件有着独立的生命周期。
- 每个 Effect 描述了一个独立的同步过程,可以 开始 和 停止。
- 在编写和读取 Effect 时,要独立地考虑每个 Effect(如何开始和停止同步),而不是从组件的角度思考(如何挂载、更新或卸载)。
将事件从Effect中分开
-
useEffectEvent
-
为了避免当依赖中的数变化时,useEffect就需要执行的问题,将部分依赖放到useEffectEvent中。
-
在useEffectEvent中的依赖变化,不会引发useEvent的更新,但更新的内容会在执行useEffectEvent的执行结果中体现。
-
这是哪门子的用法,搞不懂?
- 区分哪些依赖是需要响应式的,哪些依赖是不需要响应式的?
-
-
-
用法示例
移除 Effect 依赖
-
你不能“选择” Effect 的依赖。每个被 Effect 所使用的响应式值,必须在依赖中声明。依赖是由 Effect 的代码决定的。
- 响应式值 包括 props 以及所有你直接在组件中声明的变量和函数。
-
🔴 避免像这样抑制 linter 的警告或错误提示:
- // eslint-ignore-next-line react-hooks/exhaustive-deps
-
移除非必需的依赖
-
- 事件要放到事件函数里onClick\onSubmit,而不是useEffect里
-
Effect 是否在做几件不相关的事情?
- 两件不同的事情由两个独立的 Effect 来同步。两个独立的 Effect 有两个独立的依赖,所以它们不会在无意中相互触发。
-
-
function ChatRoom({ roomId }) { const [messages, setMessages] = useState([]); useEffect(() => { const connection = createConnection(); connection.connect(); connection.on('message', (receivedMessage) => { // 这里依赖了messages的读取,所以setMessages引发了messages的变化,又会重新调用useEffect,引发重复循环调用。 setMessages([...messages, receivedMessage]); }); // ... -
function ChatRoom({ roomId }) { const [messages, setMessages] = useState([]); useEffect(() => { const connection = createConnection(); connection.connect(); connection.on('message', (receivedMessage) => { // 把messages的读取改为更新函数 setMessages(msgs => [...msgs, receivedMessage]); }); return () => connection.disconnect(); }, [roomId, messages]); // ✅ 所有依赖已声明 // ...
-
-
如果你想读取最新值而不“反应”它,请从 Effect 中提取出一个 Effect Event。
-
在 JavaScript 中,如果对象和函数是在不同时间创建的,则它们被认为是不同的。
-
尽量避免对象和函数依赖。将它们移到组件外或 Effect 内。
-
自定义Hook
-
Hook 的名称必须以 use 开头,然后紧跟一个大写字母,就像内置的 useState 或者本文早前的自定义 useOnlineStatus 一样。
-
Hook 可以返回任意值。
-
自定义 Hook 共享的只是状态逻辑而不是状态本身。对 Hook 的每个调用完全独立于对同一个 Hook 的其他调用。
- 每当组件重新渲染,自定义 Hook 中的代码就会重新运行。
-
不要创建像 useMount 这样的自定义 Hook。保持目标具体化。