笔记记录的是React18版本。
创建React项目
1、使用CreateReactApp进行创建
npx create-react-app my-app
2、使用vite进行创建
# 以下只是事例,可以灵活使用
# 使用npm,之后再选择模板
npm create vite@latest
# 使用pnpm指定模板
pnpm create vite my-react-app --template react-ts
创建Root并渲染
使用创建工具生成React项目之后,会自动生成相关代码,在index.tsx中,代码如下:
const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
常用API
React.Component
React组件的基类,创建React类组件一般继承自此类。
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
React.PureComponent
与React.Component类似,但实现了shouldComponentUpdate方法,对props和state进行了浅比较。
若想自定义比较方法,可使用shouldComponentUpdate自定义。
React.memo
React提供的高阶组件,若组件在相同prop下渲染的结果相同,这可以使用此方法对组件进行缓存。可通过第二个参数自定义比较函数。
记录组件渲染结果,提高组件性能。比较函数返回true不更新,返回false更新。
function MyComponent(props) {
/* 使用 props 渲染 */
}
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
}
export default React.memo(MyComponent, areEqual);
注意:
与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。
React.createElement
React渲染函数,使用较少,一般直接使用jsx。
React.createElement('div', {onClick: () => console.log('点击了div')}, '点我');
React.cloneElement
克隆React元素。
React.isValidElement
判断一个对象是否是React元素。
React.Children
提供了用于处理 this.props.children 不透明数据结构的实用方法。【更多】
ReactDOM.createPortal(child, container)
将组件渲染到指定容器(类似于Vue中的Teleport组件)。虽然被渲染到了别的位置,但是节点行为与普通节点相同,包括事件的冒泡机制。
render() {
return ReactDOM.createPortal(
this.props.children,
document.body
);
}
ReactDOM.flushSync(callback)
强制 React 同步刷新提供的回调函数中的任何更新。这确保了 DOM 会被立即 更新。
React.createContext(defaultValue)
创建一个Context,只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
通过Context.Provider组件包裹使用value来传递数据。
通过compenent.contextType = Context或者static contextType = Context 来给组件绑定Context,组件中就可以使用this.context获取值了。
当有多个Context时,可以使用Context.Consumer组件来接收多个Context传递的值:{value => <Component val={value} />}此方法不需要上面的绑定了。
// 创建Content
export const TestContent = React.createContext('默认value')
// 祖先元素上层使用Provider包裹
render() {
return (
<>
{/* 使用Provider,this.context将获取到传递的value */}
<TestContent.Provider value='传递的value'>
<Test1></Test1>
</TestContent.Provider>
{/* 不使用Provider,this.context将获取到默认值 */}
<Test1></Test1>
</>
)
}
// 子孙元素通过 static contextType = TestContent 或者 Test1.contextType = TestContent来绑定Context,就可以在组件下使用this.context了。
export class Test1 extends React.Component {
// 使用 static 这个类属性来初始化 contextType。
static contextType?: React.Context<any> | undefined = TestContent;
// ...
componentDidMount() {
console.log(this.context)
}
//...
}
// 或者使用 component.contextType初始化 contextType。
Test1.contextType = TestContent
当有多个Context时,可以使用Content.Consumer来接收,并通过props传递给子孙组件。(当然,此组件在只有一个Content时也可以使用。)
注意:使用Content.Consumer之后,就不需要在子孙组件下指定contextType了,因为数据是直接通过props传递的。
// 创建两个Content
export const TestContent = React.createContext('默认test')
export const Test1Content = React.createContext('默认test1')
// 祖先元素上层使用Provider包裹,使用Consumer接收
render() {
return (
<TestContent.Provider value='传递的test'>
<TestContent.Consumer>
{test => (
<Test1Content.Provider value='传递的test1'>
<Test1Content.Consumer>
{test1 => (
<Test1 test={test} test1={test1}></Test1>
)}
</Test1Content.Consumer>
</Test1Content.Provider>
)}
</TestContent.Consumer>
</TestContent.Provider>
)
}
export class Test1 extends React.Component {
// ...
componentDidMount(): void {
console.log(this.props.test, this.props.test1)
}
// ...
}
React.createRef
创建一个能够通过 ref 属性附加到 React 元素的 ref。
React 会在组件挂载时给 current 属性传入 DOM 元素,并在组件卸载时传入 null 值。ref 会在 componentDidMount 或 componentDidUpdate 生命周期钩子触发前更新。
不能在函数组件上使用,因为没有实例,但可以通过React.forwardRef高阶组件来实现函数组件上绑定ref
export class Test1 extends React.Component {
ref: React.RefObject<HTMLInputElement>;
constructor(props: any) {
super(props)
this.ref = React.createRef()
}
componentDidMount(): void {
this.ref.current?.focus()
}
render(): React.ReactNode {
return (
<div>
<input type="text" ref={this.ref} />
</div>
)
}
}
组件中使用ref属性时,也可以传递一个回调函数,该函数接收一个节点实例参数。
refFn(e: HTMLInputElement) {
// 将打印input元素
console.log(e)
}
render(): React.ReactNode {
return (
<div>
<input type="text" ref={this.refFn} />
</div>
)
}
React.forwardRef
创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
// 将组件定义的ref转发到组件内的原生元素上。
export const Test2 = React.forwardRef((props: any, ref: React.LegacyRef<HTMLInputElement>) => {
return (
<input type="text" ref={ref} />
)
})
// 将定义在高阶组件上的ref,转发到高阶组件所包裹的原组件上。
function logProps(Component: React.Component) {
class LogProps extends React.Component {
componentDidUpdate(prevProps) {
console.log('old props:', prevProps);
console.log('new props:', this.props);
}
render() {
const {forwardedRef, ...rest} = this.props;
// 将自定义的 prop 属性 “forwardedRef” 定义为 ref
return <Component ref={forwardedRef} {...rest} />;
}
}
// 注意 React.forwardRef 回调的第二个参数 “ref”。
// 我们可以将其作为常规 prop 属性传递给 LogProps,例如 “forwardedRef”
// 然后它就可以被挂载到被 LogProps 包裹的子组件上。
return React.forwardRef((props, ref) => {
return <LogProps {...props} forwardedRef={ref} />;
});
}
React.lazy(() => import(''))
定义一个动态加载的组件。一般用于路由的懒加载上。
const SomeComponent = React.lazy(() => import('./SomeComponent'));
可使用React提供的React.Suspense组件,对加载时间白屏的处理。
React.startTransition(callback)
传递一个函数,用于执行非紧急的任务。将紧急任务和非紧急任务区分开,使UI更新更加人性化。
看下图input输入框,在未使用startTransition时,有很明显的卡顿,使用之后卡顿消失。
const list: string[] = []
for(let i = 0; i < 30000; i++) {
list.push(String(i))
}
export class Test1 extends React.Component<{}, {
keyword: string,
showList: JSX.Element[]
}> {
handleInputChangeBind: (e: any) => void;
constructor(props: any) {
super(props)
this.state = {
keyword: '',
showList: []
}
this.handleInputChangeBind = this.handleInputChange.bind(this)
}
handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
this.setState({
keyword: e.target.value
})
// 处理不紧急的渲染
React.startTransition(() => {
this.filterList(e.target.value)
})
}
// 过滤加高亮等复杂操作
filterList(keyword: string) {
this.setState({
showList: list.filter(item => item.includes(keyword)).map(item => {
const idx = item.indexOf(keyword)
return (
<div key={item}>
{item.slice(0, idx)}
<span style={{color: 'pink'}}>{item.slice(idx, idx+keyword.length)}</span>
{item.slice(idx + keyword.length)}
</div>
)
})
})
}
render(): React.ReactNode {
return (
<div>
<input type="text" onChange={this.handleInputChangeBind} />
{ this.state.showList }
</div>
)
}
}
component.forceUpdate(callback)
强制组件更新,会调用组件的reader方法,但会跳过组件的shouldComponentUpdate方法。不会影响子组件的生命周期。
component.defaultProps
通过component.defaultProps={}定义默认props
this.setState
设置state,可传递一个对象,或者传递一个返回对象的函数,会与组件的state进行合并,此方法可能是同步执行,也可能是异步执行。
render prop
一个用于告知组件需要渲染什么内容的函数。可以使用render prop来实现组件逻辑的复用。可以配合高阶组件来实现一个不需要render prop的组件。(属性名并不一定非要用render)。
注意:
render prop 与 React.PureComponent 一起使用时要小心,传递render如果是一个函数,且是在jsx中创建的,则React.PureComponent将始终会更新。可以将函数单独提出来,再使用。
一般用于封装一些公共的逻辑。下面为官方案例:
class Cat extends React.Component {
render() {
const mouse = this.props.mouse;
return (
<img src="/cat.jpg" style={{ position: 'absolute', left: mouse.x, top: mouse.y }} />
);
}
}
class Mouse extends React.Component {
constructor(props) {
super(props);
this.handleMouseMove = this.handleMouseMove.bind(this);
this.state = { x: 0, y: 0 };
}
handleMouseMove(event) {
this.setState({
x: event.clientX,
y: event.clientY
});
}
render() {
return (
<div style={{ height: '100vh' }} onMouseMove={this.handleMouseMove}>
{/*
使用 `render`prop 动态决定要渲染的内容,
而不是给出一个 <Mouse> 渲染结果的静态表示
*/}
{this.props.render(this.state)}
</div>
);
}
}
class MouseTracker extends React.Component {
render() {
return (
<div>
<h1>移动鼠标!</h1>
<Mouse render={mouse => (
<Cat mouse={mouse} />
)}/>
</div>
);
}
}
通过高阶组件实现一个不需要render props的功能复用。
function withMouse(Component) {
return class extends React.Component {
render() {
return (
<Mouse render={mouse => (
<Component {...this.props} mouse={mouse} />
)}/>
);
}
}
}
过期的api
ReactDOM.render被ReactDOM.createRoot+root.render取代。
ReactDOM.hydrate被ReactDOM.hydrateRoot取代。
ReactDom.unmountComponentAtNode被root.unmount取代。
findDOMNode已被弃用,可以使用ref替代。
常用组件
React.Fragment
能够在不额外创建 DOM 元素的情况下,让 render() 方法中返回多个元素。可以简写成<></>
<React.Fragment>
Some text.
<h2>A heading</h2>
</React.Fragment>
<!-- 或者使用简写 -->
<>
<li>Some text.</li>
<li>A heading</li>
</>
React.StrictMode
使用严格模式,为其后代元素触发额外的检查和警告。仅在开发模式下运行;它们不会影响生产构建。
<React.StrictMode>
<App />
</React.StrictMode>
React.Suspense
提供了一个组件,可以指定加载指示器(fallback属性,接收一个组件),以防其组件树中的某些子组件尚未具备渲染条件。
// 该组件是动态加载的
const OtherComponent = React.lazy(() => import('./OtherComponent'));
function MyComponent() {
return (
// 显示 <Spinner> 组件直至 OtherComponent 加载完成
<React.Suspense fallback={<Spinner />}>
<div>
<OtherComponent />
</div>
</React.Suspense>
);
}
React.Profiler
组件来检测包裹组件的性能,传递两个props(id,onRender),组件可以多次使用,嵌套使用,仅在开发模式有效,
function callback() {
/*
id, // 发生提交的 Profiler 树的 “id”
phase, // "mount" (如果组件树刚加载) 或者 "update" (如果它重渲染了)之一
actualDuration, // 本次更新 committed 花费的渲染时间
baseDuration, // 估计不使用 memoization 的情况下渲染整棵子树需要的时间
startTime, // 本次更新中 React 开始渲染的时间
commitTime, // 本次更新中 React committed 的时间
interactions // 属于本次更新的 interactions 的集合
*/
console.log(arguments)
}
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<Profiler id="Navigation" onRender={callback}>
<App />
</Profiler>,
)
Context.Provider
Context的提供者组件,通过value传递数据。
<Context.Provider value={value}>
...
</Context.Provider>
Context.Consumer
接收Context传递的值,用于解决有多个Context时的问题。
<Content.Consumer>
{test => (
<Test1 test={test}></Test1>
)}
</Content.Consumer>
类组件的生命周期
现有的生命周期
- constructor():构造函数,组件实例化时调用
- render():渲染函数,初次挂载及后续更新时调用
- componentDidMount():组件挂载后调用
- componentDidUpdate():组件更新后调用
- componentWillUnmount():组件卸载前调用
- shouldComponentUpdate():设置组件是否需要更新,可通过对比props和state,返回一个Boolean类型的值。true更新,false不更新。如果只是简单的浅比较,可以让组件继承自React.PureComponent。(需要注意的是,对于数组或对象这种引用类型的值,改变时需要替换为新创建的值,而不要直接在原数据上修改,否则比较时就是自身与自身进行比较,组件不会更新。)
- static getDerivedStateFromProps():会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回 null 则不更新任何内容。
- getSnapshotBeforeUpdate(): 在最近一次渲染输出(提交到 DOM 节点)之前调用。
- static getDerivedStateFromError():此生命周期会在后代组件抛出错误后被调用。
- componentDidCatch():此生命周期在后代组件抛出错误后被调用。
使用static getDerivedStateFromError()或componentDidCatch()两个生命周期的一个或两个,来实现一个错误边界组件。
过时的生命周期
UNSAFE_componentWillMount():在挂载之前被调用。
UNSAFE_componentWillReceiveProps():在已挂载的组件接收新的 props 之前被调用。
UNSAFE_componentWillUpdate():组件更新之前调用。
生命周期执行顺序
挂载:constructor、static getDerivedStateFromProps、render、componentDidMount。
更新:static getDerivedStateFromProps、shouldComponentUpdate、render、getSnapshotBeforeUpdate、componentDidUpdate。
卸载:componentWillUnmount。
错误处理:static getDerivedStateFromError、componentDidCatch。
Hooks
Hook是什么:Hook 是一个特殊的函数,它可以让你“钩入” React 的特性。
使用规则
1、只能在函数最外层调用 Hook。不要在循环、条件判断或者子函数中调用。
2、只能在 React 的函数组件或者自定义 Hook 中调用 Hook。不要在其他 JavaScript 函数中调用。
3、Hook在组件中可以多次使用。
因为React函数组件在组件更新时,是通过Hook的执行顺序来保持状态的,组件更新时,函数组件会再次执行,执行到Hook时,会读取创建时Hook对应名字的状态,忽略掉参数,从而保证状态不被重置。这就体现了类组件的优点了,组件更新时不再实例化类,使用原来的实例化,从而保留状态。
常用Hook
useState
使用方法:
const [state, setState] = useState(initialState)
定义一个state,initialState仅在函数组件初次渲染时有效,后续更新将被忽略。也可以传一个函数
const [state, setState] = useState(() => {
const initialState = someExpensiveComputation(props);
return initialState;
});
事例:
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
useEffect
使用方法:
useEffect(() => { ... return () => {}},[deps])
生命周期Hook,相当于componentDidMount、componentDidUpdate、componentWillUnmount,但不是同步调度。
同步调度请使用useLayoutEffect,此Hook的调度方式与上面的三个生命周期一致,而useEffect不会阻塞浏览器更新屏幕。
第一个参数为函数,在组件挂载及更新时调用,此函数可以再返回一个函数,会在组件将要卸载时调用。
第二个参数传递一个数组,指定哪些值变化时触发更新,可以通过设置一个空数组,来使此Hook仅在挂载时执行,在进入页面获取请求时可以用到。
在不传递第二个参数时,组件更新函数和卸载函数再每次组件更新都会执行。
useEffect(() => {
// 创建及更新组件时订阅。
const subscription = props.source.subscribe();
return () => {
// 卸载组件时清除订阅,此方法在每次更新前都会调用
subscription.unsubscribe();
};
});
useEffect(
async () => {
const data = await fetch('xxx');
setDate(data)
},
// 第二个参数传递一个空数组,使此Hook仅在创建时执行一次
[],
);
useEffect(
async () => {
const data = await fetch('xxx', {id: this.props.id});
setDate(data)
},
// 第二个参数传递更新依赖的参数,仅在变化时执行Hook
[this.props.id],
);
useLayoutEffect
使用方法:
useLayoutEffect(effect, deps)
其函数签名与 useEffect 相同,但它会在所有的 DOM 变更之后同步调用 effect。使用 useEffect 调度的 effect 不会阻塞浏览器更新屏幕,这让你的应用看起来响应更快。大多数情况下,effect 不需要同步地执行。在个别情况下(例如测量布局),有单独的 useLayoutEffect Hook 供你使用,其 API 与 useEffect 相同。
useContext
使用方法:
const value = useContext(Context)
获取Context.Provider组件传递的Value,使用此Hook后,在父组件value值变化时,此函数组件一定会触发更新,不管有没有使用React.memo或者shouldComponentUpdate。
// 祖先传递
<TestContent.Provider value='传递的test'>
<Test2></Test2>
</TestContent.Provider>
// 子孙接收
const value = useContext(TestContent)
console.log(value)
useReducer
使用方法:
const [state, dispatch] = useReducer(reducer, initialArg, init)
useState 的替代方案。
它接收一个形如 (state, action) => newState 的 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
第二个参数为初始值,第三个参数为可选参数,是一个函数,通过接收第二个参数来返回一个新的数据作为初始值,一般用于初始值依赖prop。
// 处理函数
function reducer(state, action) {
switch(action.type) {
case 'increment':
return { count: state.count + 1 }
break;
case 'decrement':
return { count: state.count - 1 }
default:
return state;
}
}
export const Test1 = () => {
// 定义初始值,及处理函数
const [state, dispatch] = useReducer(reducer, 0)
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
)
}
export const Test2 = (props: {init: number}) => {
// 初始值使用props传递的,使用初始函数对初始值处理一下。(仅做演示)
const [state, dispatch] = useReducer(reducer, props.init, (initial) => {
return {count: initial}
})
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
</div>
)
}
useMemo
使用方法:
const memoizedValue = useMemo(() => any, deps)
返回一个memoized值。依赖数据不变化时,创建函数就不会再次执行,直接返回缓存的数据。
export const Test2 = () => {
const [count, setCount] = useState(0)
const [count1, setCount1] = useState(0)
// 仅count变化时传递的函数才会执行
const newCount = useMemo(() => {
console.log('执行了Memo函数')
return count * 2
}, [count])
return (
<div>
count:{count}
<p>count1:{count1}</p>
newCount:{newCount}
<button onClick={() => setCount(count+1)}>设置count</button>
<button onClick={() => setCount1(count1+1)}>设置count1</button>
</div>
)
}
useCallback
使用方法:
const memoizedCallback = useCallback(fn, deps);
相当于 useMemo(() => fn, deps),返回一个 memoized 回调函数。
因函数组件更新时,函数组件会重复执行,那么在函数内部定义的函数将会重新创建。
这时候如果重新创建的函数传递给了子组件,将会导致子组件更新。
因为prop传递的函数已不是上次的那个函数了,使用此hooks可以解决这个问题。
依赖数据不变的时候,函数还是组件第一次创建的那个函数,就不会触发子组件的更新了。
(当然前提是子组件继承自React.PureComponent或者使用了shouldComponentUpdate做了prop的比较)。
export const Test2 = () => {
const [count, setCount] = useState(0)
const [count1, setCount1] = useState(0)
// 仅当count改变时,传递的函数才会重新创建
const render = useCallback(() => {
console.log('执行了渲染函数')
return <h1>{count}</h1>
}, [count])
return (
<div>
count:{count}
<p>count1:{count1}</p>
<Test3 render={render}></Test3>
<button onClick={() => setCount(count+1)}>设置count</button>
<button onClick={() => setCount1(count1+1)}>设置count1</button>
</div>
)
}
class Test3 extends React.PureComponent<{render: () => JSX.Element}, {}, any>{
constructor(props: any) {
super(props)
}
render(): React.ReactNode {
return this.props.render();
}
}
useRef
使用方法:
const refContainer = useRef(initialValue);
返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数(initialValue)。返回的 ref 对象在组件的整个生命周期内持续存在。当 ref 对象内容发生变化时,useRef 并不会通知你。变更 .current 属性不会引发组件重新渲染。
常见的用例就是命令式的访问子组件:
function TextInputWithFocusButton() {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` 指向已挂载到 DOM 上的文本输入元素
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
也可以使用ref.current来存储任何值
useImperativeHandle
使用方法:
useImperativeHandle(ref, createHandle, [deps])
可以让你在使用 ref 时自定义暴露给父组件的实例值。应当与 forwardRef 一起使用。
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);
这样就可以在不暴露子组件的情况下,对子组件进行操作了。
useDebugValue
使用方法:
useDebugValue(value, formatValueFn)
可用于在 React 开发者工具中显示自定义 hook 的标签。
useDebugValue(isOnline ? 'Online' : 'Offline');
useDebugValue(date, date => date.toDateString());
useDeferredValue
使用方法:
const deferredValue = useDeferredValue(value);
接受一个值,并返回该值的新副本,该副本将推迟到更紧急地更新之后。该 hook 与使用防抖和节流去延迟更新的用户空间 hooks 类似。useDeferredValue 仅延迟你传递给它的值。如果你想要在紧急更新期间防止子组件重新渲染,则还必须使用 React.memo 或 React.useMemo 记忆该子组件。
function Typeahead() {
const query = useSearchQuery('');
const deferredQuery = useDeferredValue(query);
// Memoizing 告诉 React 仅当 deferredQuery 改变,
// 而不是 query 改变的时候才重新渲染
const suggestions = useMemo(() =>
<SearchSuggestions query={deferredQuery} />,
[deferredQuery]
);
return (
<>
<SearchInput query={query} />
<Suspense fallback="Loading results...">
{suggestions}
</Suspense>
</>
);
}
useTransition
使用方法:
const [isPending, startTransition] = useTransition()
返回一个状态值表示过渡任务的等待状态,以及一个启动该过渡任务的函数。将紧急任务和非紧急任务区分开,使UI更新更加人性化。与useDeferredValue类似。
const list: string[] = []
for(let i = 0; i < 30000; i++) {
list.push(String(i))
}
// 过滤加高亮等复杂操作
function filterList(keyword: string) {
return list.filter(item => item.includes(keyword)).map(item => {
const idx = item.indexOf(keyword)
return (
<div key={item}>
{item.slice(0, idx)}
<span style={{color: 'pink'}}>{item.slice(idx, idx+keyword.length)}</span>
{item.slice(idx + keyword.length)}
</div>
)
})
}
export function Test2 (){
const [keyWord, setKeyWord] = useState('')
const [showList, setShowList] = useState<JSX.Element[]>([])
const [pending, startTransition] = useTransition()
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
setKeyWord(e.target.value)
// 处理不紧急的渲染
startTransition(() => {
setShowList(filterList(e.target.value))
})
}, [])
return (
<div>
<input type="text" value={keyWord} onChange={handleInputChange} />
{ !pending && showList }
</div>
)
}
效果参考上面的React.startTransition,只是多了个状态。
useId
使用说明:
const id = useId();
用于生成横跨服务端和客户端的稳定的唯一 ID 的同时避免 hydration 不匹配的 hook。
function NameFields() {
const id = useId();
return (
<div>
<label htmlFor={id + '-firstName'}>First Name</label>
<div>
<input id={id + '-firstName'} type="text" />
</div>
<label htmlFor={id + '-lastName'}>Last Name</label>
<div>
<input id={id + '-lastName'} type="text" />
</div>
</div>
);
}
useSyncExternalStore
使用方法:
const state = useSyncExternalStore(subscribe, getSnapshot[, getServerSnapshot])
一个推荐用于读取和订阅外部数据源的 hook,其方式与选择性的 hydration 和时间切片等并发渲染功能兼容。
useInsertionEffect
同useEffect,区别在于:在组件更新前触发。
React Diff算法
算法依据
1、两个不同类型的元素会产生不同的树。
2、可以通过设置key属性标记哪些子元素在不同的渲染中可能是不变的。
比较过程
1、根节点变成不同类型的标签,则销毁旧树(componentWillUnmount),创建新树(UNSAFE_componentWillMount、componentDidMount)。
2、节点类型相同时,保留节点,仅对比改变的属性。
3、对比同类型的组件元素时,组件实例不变,以保持不同渲染时state的一致,将更新组件的props以保证与最新元素一致(UNSAFE_componentWillReceiveProps、UNSAFE_componentWillUpdate、componentDidUpdate、render)。
4、对子节点进行递归比较。同时遍历新旧虚拟DOM树的比较,生成一个mutation。这样对比会导致尾部新增元素时仅创建一个新增元素即可,但是首部新增元素时,就会重新创建所有子节点。可以通过key属性解决。
5、使用key属性来添加标记,优化比较,key应保证唯一性和不变性,尽量不要使用下标。