https://zh-hans.reactjs.org/docs/hooks-reference.html
本文不涉及 library hooks
基础 Hook
额外的 Hook
js// import
import {useState} from 'react'
/*
* state: 状态变量名称
* setState: 修改该状态值的函数名称
* initState: 默认值
*/
const [state, setState] = useState(initState)
// 如果默认值需要复杂计算获得,可以传入一个函数,该函数只有初始渲染会被调用
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
/*
* 如果新的 state 需要通过使用先前的 state 计算得出,那么可以将函数传递给 setState。
* 该函数将接收先前的 state,并返回一个更新后的值。
*/
// x: 最新的 state 值
setState(x=> x+1)
// 更改 state 为 111
setState(111) // 原理为 setState(x=> 111)
setState 的执行
js// 假设 state = 1
setState(state + 1) // 1+1
setState(state + 1) // 1+1
setState(state + 1) // 1+1
//
setState(state + 1) // 1+1
setState(x=> x+1) // 2+1
setState(47) // 47
js// 每次渲染都针对的是当前的快照,那么 state 正常是不变的
console.log(state) //1
setState(state + 2)
console.log(state) //1
通常 useEffect 进行改变 DOM、网络请求、添加订阅、设置定时器、记录日志以及执行其他包含副作用的操作
js
useEffect(
() => {
// 类似 componentDidMount、componentDidUpdate
// 执行网络请求、dom 操作、设置定时器
const subscription = props.source.subscribe();
return () => {
// 组件卸载前执行,类似 componentWillUnmount
subscription.unsubscribe();
};
},
/*
* effect 所依赖的值数组,effect 只会在依赖值变化时进行执行
* 不填写该参数时(不是指[]),effect 默认跟随组件更新指定
* 该参数为 [] 时, effec 内的 props 和 state 会保持初始值,不跟随组件更新
*/
[props.source],
);
注意
如果组件多次渲染,则在执行下一个 effect 之前,上一个 effect 就已被清除。
意味组件每次更新,在上例中都会创建新的订阅 如果不希望其他因素引起的组件更新都触发重新执行 effect,则需要指定
依赖值
,这样 effect 只会在依赖值变化时进行执行
传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
意味着操作不会阻塞浏览器对屏幕的更新 如果需要监听 dom 变更并同步执行更改,则应该使用 useLayoutEffect
有一些变量,需要在组件之间共享 state
props
进行传递useContext
来共享一般常见使用使用场景
js// 父组件
const themes = {
light: {
foreground: "#000000",
background: "#eeeeee"
},
dark: {
foreground: "#ffffff",
background: "#222222"
}
};
// 创建一个 context
// 名称为 ThemeContext,默认值为 themes.light
const ThemeContext = React.createContext(themes.light);
function App() {
return (
// 使用 provider 包裹子组件,则子组件的
<ThemeContext.Provider value={themes.dark}>
<Toolbar />
</ThemeContext.Provider>
);
}
js// 子组件
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
// 子组件的子组件
function ThemedButton() {
// 可以跨过子组件拿到 context
const theme = useContext(ThemeContext);
return (
<button style={{ background: theme.background, color: theme.foreground }}>
I am styled by theme context!
</button>
);
}
注意
useReducer 是 useState 的替代方案,主要用于
add
delete
,或状态 isActive
工作模式类似 redux。 它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
jsconst initialState = {count: 0};
// 初始化函数
function init(initialCount) {
return {count: initialCount};
}
// 根据 dispatch 鞋带的 action 信息,处理 state
// 函数返回值就是 state 的结果
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() {
/*
* state: 状态变量
* dispatch: 携带 action 的 dispatch 名称
* reducer: 处理该 dispatch 的函数
* initialState: 默认值,可以直接填入值
* initFn: 惰性初始化函数,可选
*/
const [state, dispatch] = useReducer(reducer, initialState, initFn);
return (
<>
Count: {state.count}
<button
/* action 一般都是一个对象,携带 type 属性,可以有其他属性*/
onClick={() => dispatch({type: 'reset', payload: initialCount})}>
Reset
</button>
<button
/* 发送一个 dispatch,指定操作 action */
onClick={() => dispatch({type: 'decrement'})}
>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
相关信息
useReducer 和 useState 变化处理相同,如果返回值与当前 state 相同,react 不会进行组件更新
useCallback是用来优化性能的,
useCallback返回一个 memoized(持久化) 回调函数,不会随组件更新生成新的函数,只有在依赖项变化的时候才会更新(返回一个新的函数)。
jsconst memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
js// 参考: https://www.jianshu.com/p/be8fb469d507
// 用于记录 getData 调用次数
let count = 0;
function App() {
const [val, setVal] = useState("");
function getData() {
setTimeout(() => {
setVal("new data " + count);
count++;
}, 500);
}
return <Child val={val} getData={getData} />;
}
function Child({val, getData}) {
useEffect(() => {
getData();
}, [getData]);
return <div>{val}</div>;
}
这里的例子中
如果把 getData 从依赖中去除,则 getData 就只能执行一次 而实际情况可能,getData 改变时,是需要重新获取数据的。 这时,可以使用 useCallback 把 getData 持久化 这样,父组件传给子组件的 getData 函数就是稳定的,只有特定条件才会更新
js// 父组件中使用 useCallback 将函数持久化
const getData = useCallback(() => {
setTimeout(() => {
setVal("new data " + count);
count++;
}, 500);
}, []);
如果在 getData 中还需要使用到 val(useState 中的值),那么就只能将 val 加入依赖,这样的话,getData 又会死循环创建新的函数
jsconst getData = useCallback(() => {
console.log(val);
setTimeout(() => {
setVal("new data " + count);
count++;
}, 500);
}, [val]);
如果我们希望无论val怎么变,getData的引用都保持不变,同时又能取到val最新的值,可以通过自定义 hook 实现。 注意这里不能简单的把val从依赖列表中去掉,否则getData中的val永远都只会是初始值(闭包原理)。
js// 这里实现解决的原理是: 使用useRef 将函数引用保留
// 这样就不用重新创建新函数,只是更换了里面的函数内容
function useRefCallback(fn, dependencies) {
const ref = useRef(fn);
// 每次调用的时候,fn 都是一个全新的函数,函数中的变量有自己的作用域
// 当依赖改变的时候,传入的 fn 中的依赖值也会更新,这时更新 ref 的指向为新传入的 fn
useEffect(() => {
ref.current = fn;
}, [fn, ...dependencies]);
return useCallback(() => {
const fn = ref.current;
return fn();
// 这里返回的最终函数只受 ref 影响
}, [ref]);
}
// 使用自定义钩子
const getData = useRefCallback(() => {
console.log(val);
setTimeout(() => {
setVal("new data " + count);
count++;
}, 500);
}, [val]);
共同作用: 1.只有 依赖数据 发生变化, 才会重新计算结果,也就是起到缓存的作用。
两者区别: 1.useMemo 计算结果是 return 回来的值, 主要用于 缓存计算结果的值 ,应用场景如: 需要 计算的状态 2.useCallback 计算结果是 函数, 主要用于 缓存函数,应用场景如: 需要缓存的函数,因为函数式组件每次任何一个 state 的变化 整个组件 都会被重新刷新,一些函数是没有必要被重新刷新的,此时就应该缓存起来,提高性能,和减少资源浪费。
简而言之,useMemo是用来缓存计算属性的。 计算属性其实是函数的返回值,或者说指那些以返回一个值为目标的函数。 直接在渲染的时候就执行,在DOM区域被当作属性值一样去使用,被称为计算属性。 而计算属性,最后一定会使用return返回一个值!
jsconst Com = () => {
const [params1,setParams1] = useState(0);
const [params2,setParams2] = useState(0);
//这种是需要我们手动去调用的函数
const handleFun1 = () => {
console.log("我需要手动调用,你不点击我不执行");
setParams1(val => val +1);
}
//这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。
const computedFun2 = () => {
console.log('我又执行计算了');
return params2;
}
return <div onClick = {handleFun1}>
//每次重新渲染的时候我就会执行
computed:{computedFun2()}
</div>
}
每次组件重新渲染都会让我们去执行computedFun2函数(计算属性) 尽管computedFun2函数中只使用到了params2状态,与被改变的状态并没有任何关系。
如果computedFun2函数里面的计算过程非常的复杂,那么每次重新计算无疑的非常麻烦的。
useMemo 可以让组件,在改变与计算属性无关的状态的时候,进行的渲染不触发我们计算属性的重新计算
jsconst Com = () => {
const [params1,setParams1] = useState(0);
const [params2,setParams2] = useState(0);
//这种是需要我们手动去调用的函数
const handleFun1 = () => {
console.log("我需要手动调用,你不点击我不执行");
setParams1(val => val +1);
}
//这种被称为计算属性,不需要手动调用,在渲染阶段就会执行的。
const computedFun2 = useMemo(() => {
console.log('我又执行计算了');
return params2;
},[params2])
return <div onClick = {handleFun1}>
//现在,我被useMemo保护,只有在组件初始化和params2改变的时候会执行
computed:{computedFun2()}
</div>
}
注意
useMemo是不是用的越多越好
缓存,需要成本!
在组件进行渲染并且此组件内使用了useMemo之后,为了校验改组件内被useMemo保护的这个计算属性是否需要重新计算,它会先去useMemo的工作队列中找到这个函数,然后还需要去校验这个函数都依赖是否被更改。 这其中,寻找到需要校验的计算属性和进行校验这两个步骤都需要成本。
jsconst Com = () => {
//这种就是完全没必要被useMemo缓存的,计算过程一共也就一个创建变量,一个加一,缓存它反而亏本
const computedFun1 = () => {
let number = 0;
number = numebr +1;
return number;
}
//这个就需要缓存一下了,毕竟他每次计算的计算量还是蛮大的。
const computedFun2 = () => {
let number = 0;
for(let i=0;i<100000;++i){
number = number +i-(number-i*1.1);
}
return number;
}
return <div onClick = {handleFun1}>
computed1:{computedFun1()} //这个计算量小,是不需要使用useMemo缓存的,缓存它反而亏本
computed2:{computedFun2()} //这个计算量大,需要缓存。
</div>
}
React.memo()其实是通过校验Props中的数据的内存地址是否改变来决定组件是否重新渲染组件的一种技术。
子组件传入一个计算属性,当父组件的其他State(与子组件无关的state)改变的时候。那么,因为状态的改变,父组件需要重新渲染,那被React.memo保护的子组件是否会被重新构建?
jsimport {useMemo,memo} from 'react';
/**父组件**/
const Parent = () => {
const [parentState,setParentState] = useState(0); //父组件的state
//需要传入子组件的函数
const toChildComputed = () => {
console.log("需要传入子组件的计算属性");
return 1000;
}
return (<div>
<Button onClick={() => setParentState(val => val+1)}>
点击我改变父组件中与Child组件无关的state
</Button>
//将父组件的函数传入子组件
<Child computedParams={toChildComputed()}></Child>
<div>)
}
/**被memo保护的子组件**/
const Child = memo(() => {
consolo.log("我被打印了就说明子组件重新构建了")
return <div><div>
})
在上面这个例子中,是会被重新构建的
React.memo检测的是props中数据的栈地址是否改变 这里如果传入的是一个函数,如果该函数没有被缓存的话,栈地址会变化,也就会引起子组件的重新渲染。 这种情况可以使用 usememo 来缓存子组件接受的 props 来解决。 不用 useCallback 来缓存是因为,这里传入的是函数的结果,缓存的函数生成的结果仍然是新值,是一个新的存储地址
useRef 返回一个可变的 ref 对象,其 .current
属性被初始化为传入的参数(initialValue)。
返回的 ref 对象在组件的整个生命周期内持续存在
。
jsxfunction TextInputWithFocusButton() {
// 创建一个 ref 对象
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
/* 关联 ref */
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
注意
回调 ref
来实现(见下)。ref 属性只是 dom 上的一个属性,用于指向 dom,方便操作
useRef() 比 ref 属性更有用,不仅可以用于 DOM refs,它可以很方便地保存任何可变值
「ref」 对象是一个 current 属性可变且可以容纳任意值的通用容器,类似于一个 class 的实例属性。
js// 可以直接使用 useRef 存储非 dom 元素
function Timer() {
const intervalRef = useRef();
useEffect(() => {
const id = setInterval(() => {
// ...
});
intervalRef.current = id;
return () => {
clearInterval(intervalRef.current);
};
});
// 定时器一旦使用 useRef 固定,就非常方便操作了
function handleCancelClick() {
clearInterval(intervalRef.current);
}
// ...
}
ref 属性可以是一个回调函数,该函数的参数为 dom 本身,可通过回调对 dom 进行定制化操作
获取 DOM 节点的位置或是大小的基本方式是使用 callback ref 每当 ref 被附加到一个另一个节点,React 就会调用 callback。
jsfunction MeasureExample() {
const [height, setHeight] = useState(0);
// 回调传入一个 dom 本身
const measuredRef = useCallback(node => {
// 回调内容对 dom 进行操作
if (node !== null) {
setHeight(node.getBoundingClientRect().height);
}
}, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1>
<h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
在这个案例中,我们没有选择使用 useRef,因为当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们,也就不会触发更新。
使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。
我们传递了 [] 作为 useCallback 的依赖列表。这确保了 ref callback 函数不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。
useImperativeHandle的作用是将子组件的指定元素暴漏给父组件使用。 也就是父组件可以访问到子组件内部的特定元素。 在大多数情况下,应当避免使用这样的命令式代码。
useImperativeHandle
应当与 forwardRef
一起使用:
js// 父组件
import {useRef} from "react"
import Son from "./son"
export default function(){
const eleP = useRef()
const getElement = () => {
console.log(eleP.current.ele1.current)
console.log(eleP.current.ele2.current)
}
return <div>
<button onClick={()=>getElement()}>点击获取子组件元素</button>
// 提供保存子组件提供的元素的变量 ref
<Son ref={eleP}/></div>
}
// 点击后触发结果:
//<h2></h2>
//<h3></h3>
js// 子组件
import {useRef,forwardRef,useImperativeHandle} from "react"
// ref 由父组件提供
function Son (props, ref){
const ele1 = useRef()
const ele2 = useRef()
// 使用 useImperativeHandle,将返回值提供给父组件
/*
* 参数 1:ref: 父组件提供的存储【子组件共享的元素】变量
* 参数 2:回调函数,返回一个内容存储在父组件提供的 ref 中
* 参数 3:依赖数组,当数组内数据变化,会重新触发回调函数
*/
useImperativeHandle(ref,()=>{
return {ele1,ele2}
},[])
return <div >
<h2 ref={ele1}></h2>
<h3 ref={ele2}></h3>
</div>
}
//
Son = forwardRef(Son)
export default Son
useLayoutEffect与useEffect内部的实现其实是一样的.
传给 useEffect 的函数会在浏览器完成布局与绘制之后,在一个延迟事件中被调用。
唯一不同的点在于
useEffect
是异步
处理副作用,而useLayoutEffect
是同步
处理副作用
useEffect:
useLayoutEffect:
js
.center {
text-align: center;
margin: 0;
padding: 0;
}
.square {
position: absolute;
top: 50%;
left: 0;
width: 100px;
height: 100px;
background: red;
border-radius: 50%;
}
const Demo = () => {
useEffect(() => {
const square = document.querySelector(".square");
square.style.transform = "translate(-50%, -50%)";
square.style.left = "50%";
square.style.top = "50%";
}, []);
return (
<div className="center">
<div className="square"></div>
</div>
)
};
因为useEffect是异步执行的,因此当页面渲染完毕后,square元素会直接从左上角移动到中间位置,导致页面会闪动一下。 将useEffect替换为useLayoutEffect后,页面中的圆球就直接在中间展示而不会从左上角偏移过来
相关信息
useDebugValue 用于在 React 开发者工具(如果已安装,在浏览器控制台 React 选项查看)中显示 自定义 Hook 的标签
useDebugValue的优势在于,用useDebugValue输出的值,是和DevTools中的Hook状态一起动态显示的,不需要在DevTools和Console面板中切换查看Hook状态和console.log输出。
useDebugValue()有两个参数
js// 语法示例
useDebugValue("调试信息", (val) => {
// val 就是 "调试信息" 这几个字
return "调试信息的再加工结果"
});
js//
import React, { useState, useDebugValue } from 'react';
function useDiy2(num) {
useDebugValue('111');
useDebugValue('222', (val) => {
return + Date.now() + val
});
const [count, setCount] = useState(num);
const setCountDiy = (who) => {
// 不能放在普通函数里面
// 只能放在组件或者use函数里面
// useDebugValue('888')
setCount(count + 2);
}
return [count, setCountDiy];
}
function App() {
const [count2, setCount2] = useDiy2(10);
// 可以放在组件里面,不报错但没效果
useDebugValue('111')
return (
<>
{count2}
<button
// 这不是个use,所以不能用useDebugValue
onClick={() => setCount2("333")}>
</button>
</>
)
}
export default App;
注意
在某些情况下,格式化值的显示可能是一项开销很大的操作。除非需要检查 Hook,否则没有必要这么做。
useDefferedValue 可以将值触发的计算
优先级降低
如果说某些渲染比较消耗性能,比如存在实时计算和反馈,我们可以使用这个Hook降低其计算的优先级,使得避免整个应用变得卡顿。
js// value: 默认值
// 延迟允许最长时间,到达该时间一定开始触发运算
const deferredValue = useDeferredValue(value, {timeoutMs: 1000});
<input value={text} onChange={handleChange}/>
<List text={deferredText}/>
jsimport React, {useState, useEffect} from 'react';
const List = (props) => {
const [list, setList] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count => count + 1);
setTimeout(() => {
setList([
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
]);
}, 500);
}, [props.text]);
return [<p>{'我被触发了' + count + '次'}</p>
, <ul>{list.map(item => <li>Hello:{item.name} value:{item.value}</li>)}</ul>]
};
export default function App() {
const [text, setText] = useState("哈哈哈");
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div className="App">
<input value={text} onChange={handleChange}/>
<List text={text}/>
</div>
);
};
一般我们的代码是这样写的。输入框的值变化的时候,我们使用setTimeout来模拟下向后端请求数据,或者大量计算的耗时操作。这个时候只要输入框的内容发生变化就会触发useEffect,我们用count来进行计数。
这样,List的Text变量就是一个延迟更新的值
useDeferredValue:
timeoutMs
这个参数的含义是延迟的值允许延迟多久,事实上网络和设备允许的情况下,React会尝试使用更低的延迟。防抖:
jsimport React, {useState, useEffect, useDeferredValue} from 'react';
import {useDebounce} from 'ahooks';
const List = (props) => {
const [list, setList] = useState([]);
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count => count + 1);
setTimeout(() => {
setList([
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
{name: props.text, value: Math.random()},
]);
}, 500);
}, [props.text]);
return [<p>{'我被触发了' + count + '次'}</p>,
<ul>{list.map(item => <li>Hello:{item.name} value:{item.value}</li>)}</ul>]
};
export default function App() {
const [text, setText] = useState("哈哈哈");
// useDefferedvalue
const deferredText = useDeferredValue(text, {timeoutMs: 1000});
// 防抖
const debouncedValue = useDebounce(text, { wait: 1000 });
const handleChange = (e) => {
setText(e.target.value);
};
return (
<div className="App">
<input value={text} onChange={handleChange}/>
<List text={deferredText}/>
<List text={debouncedValue}/>
</div>
);
};
useTransition
可以将一个函数的操作
降级
js// 案例
import {useState} from "react";
import styles from "./index.module.css";
const Home:React.FC = () => {
const [val,setVal] = useState('');
const [arr,setArr] = useState<number[]>([]);
//根据用户输入获取查询列表
const getList = (e:any) => {
setVal(e.target.value)
let arr = Array.from(new Array(10000),item=>Date.now())
setArr(arr);
}
return (
<div className={styles.box}>
<input value={val} onChange={getList}/>
{
arr.map((item,key)=>(
<div key={key}>{item}</div>
))
}
</div>
)
}
export default Home;
我们快速在表单输入了abcd,但是很明显出现了卡顿现象,大约2s后表单和列表数据都被渲染
由于我们使用了useState对表单和列表的数据进行了更新,二者触发批量更新机制但无优先级差异。
实际上为了保证用户体验感,我们需要保证输入框的输入数据永远处于最新显示,而列表的提示可以稍微滞后显示。
js// 使用说明
/*
* pending: 是否正在排队等待执行
* transition: startTransition 函数名称,使用该函数包裹的内容将被降级执行
*/
import {useState,startTransition} from "react";
const [pending,transition] = useTransition();
jsimport React,{useState,useTransition} from "react";
const Home:React.FC = () => {
const [val,setVal] = useState('');
const [arr,setArr] = useState<number[]>([]);
// 创建hook
const [pending,transition] = useTransition()
const getList = (e:any) => {
setVal(e.target.value)
let arr = Array.from(new Array(10000),item=>Date.now())
// 这里将大运算进行了降级排队执行
transition(()=>{
setArr(arr);
})
}
return (
<div className={styles.box}>
<input value={val} onChange={getList}/>
{
/* 根据运算状态渲染 */
pending?<h2>loading....</h2>:(
arr.map((item,key)=>(
<div key={key}>{item}</div>
))
)
}
</div>
)
}
export default Home;
useId 用来生成一个唯一的 id 值,其实质是组件的层级结构得来的
js// App.tsx
const id = Math.random();
export default function App() {
return <div id={id}>Hello</div>
}
如果应用是CSR(客户端渲染),id是稳定的,App组件没有问题。 但如果应用是SSR(服务端渲染),那么App.tsx会经历:
React在服务端渲染,生成随机id(假设为0.1234),这一步叫dehydrate(脱水)
<div id="0.12345">Hello</div>
作为HTML传递给客户端,作为首屏内容
React在客户端渲染,React 还需要对该组件重新激活,用于参与新的渲染更新等过程中,生成随机id(假设为0.6789),这一步叫hydrate(注水)
客户端、服务端生成的id不匹配!
使用 useId 来解决
jsfunction Checkbox() {
// 生成唯一、稳定id
const id = useId();
return (
<>
<label htmlFor={id}>Do you like React?</label>
<input type="checkbox" name="react" id={id} />
</>
);
);
注意
jsfunction Checkbox() {
// 生成唯一、稳定id
const id_1 = useId();
// 再次使用 useId 仍是相同结果
const id_2 = id_1 + 1
return (
<>
<label htmlFor={id_1}>Do you like React?</label>
<input type="checkbox" name="react" id={id} />
</>
);
);
:
的字符串 token。这有助于确保 token 是唯一的假设应用的组件树如下图:
不管B
和C
谁先hydrate
,他们的层级结构是不变的,所以层级本身就能作为服务端、客户端之间不变的标识。
比如 B 可以使用 2-1
作为id,C 使用 2-2
作为id
如果组件A、D使用了useId,B、C没有使用,那么只需要为A、D划定层级,这样就能减少需要表示层级。
本文作者:Silon汐冷
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!