聊聊React的新特性们
前言
上次面试, 聊到React
近几年更新的大版本, 我才意识到我比较深入的认知还停留在Hooks
刚出来的时候, 近期对社区的关注也就是只是停留在知道出了18.2
, 并且已经全面拥抱Next.js
了.
比起年龄, 更让程序员焦虑的, 是对新变化的不了解.
本文不是为了详细讲解新的hooks
, 讲解肯定是官网最详细, 主要还是写一些demo帮助自己理解.
在blog中引入18.2
console.log('React version:', React.version)
console.log('ReactDOM version:', ReactDOM.version)
useLayoutEffect
这是个17.几
的版本就出来的特性了, 一直没用过.
官网的描述: useLayoutEffect 是 useEffect 的一个版本,在浏览器重新绘制屏幕之前触发。
为了证明useLayoutEffect
对渲染有阻拦作用, 写了下面一个简单的demo:
鼠标点击红框内即加载小蓝框, 点击绿框即隐藏小蓝框.
为了能看出差别, 加了一个比较重的循环, 多点几次, 在把示例代码改成useEffect
可以明显看到有闪烁.
const { createRoot } = ReactDOM
const { useEffect, useLayoutEffect, useState } = React
const root = createRoot(document.getElementById('useLayoutEffect'))
const ChildDom = ({ position }) => {
const [state, setState] = useState({ x: 0, y: 0 })
// !!!!把这改成useEffect试试!!!!
useLayoutEffect(() => {
for (let i = 0; i < 1e8; i += 1) {
const a = Math.random()
}
setState(position)
}, [])
return (
<div style={{
position: 'absolute',
left: state.x,
top: state.y,
width: '100px',
height: '100px',
background: 'blue'
}}>
child
</div>
)
}
const App = () => {
const [childPosition, setChildPosition] = useState(null)
return (
<div
style={{
display: 'flex'
}}
>
<div
onClick={e => {
setChildPosition({
x: e.nativeEvent.offsetX,
y: e.nativeEvent.offsetY
})
}}
style={{
border: '1px solid red',
width: '200px',
height: '200px',
position: 'relative'
}}
>
click to show area
{childPosition && (
<ChildDom
position={childPosition}
/>
)}
</div>
<div
onClick={() => setChildPosition(null)}
style={{
border: '1px solid green',
width: '200px',
height: '200px',
position: 'relative'
}}
>
click to disappear area
</div>
</div>
)
}
root.render(<App />);
这个功能我知道但是一直没用过
确实是有原因的, 一时半会想不出使用的业务场景. 比如上面我写的demo
, 把组件内部的初始state
set值的那一步完全可以想办法规避掉.
18的重头戏
react18讲的最多的就是并发渲染
, 解决的是啥问题呢, 上个demo
先, 请来回切换两个button, 卡吧?
卡就对了, activeB
渲染了1万个div
.
官方提供了两个hooks
函数useTransition
和useDeferredValue
, 前者允许并发
一个与渲染activeB下div
同级别的pending
UI事件, 从而达到友善的交互. 后者延迟
一个activeB
且允许被打断的渲染, 同样提升了用户交互感受.
const { createRoot } = ReactDOM
const { useState } = React
const root = createRoot(document.getElementById('demo'))
const ChildA = () => {
console.log('renderA')
return (
<div>
childA
</div>
)
}
const ChildB = () => {
console.log('render heavy B')
return (
<div>
{new Array(1e5).fill(0).map((_, i) => (
<div key={i}>
childB
</div>
))}
</div>
)
}
const App = () => {
const [activeA, setActiveA] = useState(true)
const handleClickButton = (param) => {
setActiveA(param)
}
return (
<div>
<button
style={{ color: activeA ? 'red' : 'black' }}
onClick={() => handleClickButton(true)}
>
activate A
</button>
<button
style={{ color: !activeA ? 'red' : 'black' }}
onClick={() => handleClickButton(false)}
>
activate B
</button>
{activeA ? <ChildA /> : <ChildB />}
</div>
)
}
root.render(<App />);
useTransition
在这里例子中, 点击activeB
后, "并发"了一个pending UI
(你也可以改成activeB按钮的预激活状态), 直到ChildB
渲染完成. 从UI交互的层面来说,卡顿的感受明显的减轻了.
const { createRoot } = ReactDOM
const { useTransition, useState } = React
const root = createRoot(document.getElementById('demo-useTransition'))
const ChildA = () => {
console.log('renderA')
return (
<div>
childA
</div>
)
}
const ChildB = () => {
console.log('render heavy B')
return (
<div>
{new Array(1e5).fill(0).map((_, i) => (
<div key={i}>
childB
</div>
))}
</div>
)
}
const App = () => {
const [activeA, setActiveA] = useState(true)
const [isPending, startTransition] = useTransition();
const handleClickButton = (param) => {
startTransition(() => {
setActiveA(param)
})
}
return (
<div>
<button
style={{ color: activeA ? 'red' : 'black' }}
onClick={() => handleClickButton(true)}
>
activate A
</button>
<button
style={{ color: !activeA ? 'red' : 'black' }}
onClick={() => handleClickButton(false)}
>
activate B
</button>
{isPending ? <div>loading...</div>
: activeA ? <ChildA /> : <ChildB />}
</div>
)
}
root.render(<App />);
useDeferredValue
在这个例子中, 我将控制激活activeB按钮
和渲染activeB列表
的state拆开了, 两个'state'"并发"触发, 将较重的renderListA
放在了useDeferredValue
中.
可以看到例子中, activeB的按钮是立刻激活的, 但是列表的渲染是延迟的.
这并不是单纯的拆分两个state就可以做到, 你可以将例子中renderListA
改为const renderListA = activeA
试试.
const { createRoot } = ReactDOM
const { useDeferredValue, useState, useMemo } = React
const root = createRoot(document.getElementById('demo-useDeferredValue'))
const ChildA = () => {
console.log('renderA')
return (
<div>
childA
</div>
)
}
const ChildB = () => {
console.log('render heavy B')
return (
<div>
{new Array(1e5).fill(0).map((_, i) => (
<div key={i}>
childB
</div>
))}
</div>
)
}
const App = () => {
const [activeA, setActiveA] = useState(true)
const renderListA = useDeferredValue(activeA)
return (
<div>
<button
style={{ color: activeA ? 'red' : 'black' }}
onClick={() => setActiveA(true)}
>
activate A
</button>
<button
style={{ color: !activeA ? 'red' : 'black' }}
onClick={() => setActiveA(false)}
>
activate B
</button>
{renderListA ? <ChildA /> : <ChildB />}
</div>
)
}
root.render(<App />);