我如何使用react-hooks实现 getSnapshotBeforeUpdate 给我的相同逻辑?
getSnapshotBeforeUpdate 使用react-hooks
按照该阵营钩常见问题,没有实现的方式getSnapshotBeforeUpdate
和ComponentDidCatch
生命周期的方法与钩但
Hooks 是否涵盖类的所有用例?
我们的目标是让 Hooks 尽快覆盖类的所有用例。目前还没有与 uncommon
getSnapshotBeforeUpdate
和componentDidCatch
生命周期等效的 Hook ,但我们计划很快添加它们。Hooks 还处于早期阶段,因此某些集成(如 DevTools 支持或 Flow/TypeScript 类型)可能还没有准备好。某些第三方库目前也可能与 Hooks 不兼容。
我们无法在任何钩子(useLayoutEffect 或 useEffect)中获取快照数据,因为它们都会在触发时提供更新的 DOM 值,捕获数据的最佳位置是在设置状态之前。例如在这里我在设置状态之前捕获滚动位置。
function ChatBox(props){
const [state, setState] = useState({chatFetched:[],isFetching:false});
const listRef = useRef();
const previousScrollDiff = useRef(0);
// on mount
useEffect(()=>{
getSomeMessagesApi().then(resp=>{
const chatFetched = [...state.chatFetched,...resp];
setState({chatFetched});
})
},[]);
useLayoutEffect(()=>{
// use the captured snapshot here
listRef.current.scrollTop = listRef.current.scrollHeight - previousScrollDiff.current;
},[state.chatFetched])
useEffect(()=>{
// don't use captured snapshot here ,will cause jerk effect in scroll
},[state.chatFetched]);
const onScroll = (event) => {
const topReached = (event.target.scrollTop === 0);
if(topReached && !state.isFetching){
setState({...state, isFetching:true});
getSomeMessagesApi().then(resp=>{
const chatFetched = [...resp,...state.chatFetched];
// here I am capturing the data ie.., scroll position
previousScrollDiff.current = listRef.current.scrollHeight -listRef.current.scrollTop;
setState({chatFetched, isFetching:false});
})
}
}
return (
<div className="ui container">
<div
className="ui container chat list"
style={{height:'420px', width:'500px',overflow:'auto'}}
ref={listRef}
onScroll={onScroll}
>
{state.chatFetched.map((message)=>{
return <ChatLi data ={message} key ={message.key}></ChatLi>
})}
</div>
</div>
);
};
我们也可以使用Memo在dom更新发生之前捕获数据,
function ChatBox(props){
const [state, setState] = useState({chatFetched:[],isFetching:false});
const listRef = useRef();
const previousScrollDiff = useRef(0);
// on mount
useEffect(()=>{
getSomeMessagesApi().then(resp=>{
const chatFetched = [...state.chatFetched,...resp];
setState({chatFetched});
})
},[]);
useLayoutEffect(()=>{
// use the captured snapshot here
listRef.current.scrollTop = listRef.current.scrollHeight - previousScrollDiff.current;
},[state.chatFetched])
useEffect(()=>{
// don't use captured snapshot here ,will cause jerk effect in scroll
},[state.chatFetched]);
useMemo(() => {
// caputure dom info in use effect
if(scrollUl.current){
previousScrollDiff.current = scrollUl.current.scrollHeight - scrollUl.current.scrollTop;
}
}, [state.chatFetched]);
const onScroll = (event) => {
const topReached = (event.target.scrollTop === 0);
if(topReached && !state.isFetching){
setState({...state, isFetching:true});
getSomeMessagesApi().then(resp=>{
const chatFetched = [...resp,...state.chatFetched];
setState({chatFetched, isFetching:false});
})
}
}
return (
<div className="ui container">
<div
className="ui container chat list"
style={{height:'420px', width:'500px',overflow:'auto'}}
ref={listRef}
onScroll={onScroll}
>
{state.chatFetched.map((message)=>{
return <ChatLi data ={message} key ={message.key}></ChatLi>
})}
</div>
</div>
);
};
在上面的示例中,我尝试执行getSnapshotBeforeUpdate react doc 中显示的相同操作
您可以使用useMemo()
代替getSnapshotBeforeUpdate()
. 在此处阅读有关如何使用 React Hooks 记忆计算的更多信息。
这里有一个简单的例子:
它总是用户类型(的onChange)在查看列表组件的点无关的状态发生变化,因为这一点,重新渲染,可以重新呈现超过50倍它依赖于用户打字,所以它的使用useMemo()
来记住列表组件,它表示todoList
只听。
import List from './List'
const todo = (props) => {
const [inputIsValid, setInputIsValid] = useState(false)
const inputValidationHandler = (event) => {
if(event.target.value.trim() === '') {
setInputIsValid(false)
} else {
setInputIsValid(true)
}
}
return <React.Fragment>
<input
type="text"
placeholder="Todo"
onChange={inputValidationHandler}
/>
{
useMemo(() => (
<List items={todoList} onClick={todoRemoveHandler} />
), [todoList])
}
</React.Fragment>
}
export default todo
简短回答:它没有react-hooks!但是我们可以创建一个自定义的!
这就是使用useEffect()
和useLayoutEffect()
!因为它们是关键要素!
最后一个例子是最后一个!所以一定要检查它(我们的自定义钩子等效)。
useEffect() 和 useLayoutEffect()
useEffect => useEffect 异步运行,并在渲染绘制到屏幕后运行。
- 您以某种方式导致渲染(更改状态,或父级重新渲染)
- React 渲染你的组件(调用它)
- 屏幕在视觉上更新
- 然后 useEffect 运行
useEffect() => render() => dom 突变 => 重绘 => useEffect() [访问 dom 新状态](直接改变 dom)=> 重绘
==> 意思 useEffect() 就像comonentDidUpdate()
!
useLayoutEffect => useLayoutEffect,另一方面,在渲染之后但在屏幕更新之前同步运行。这些都不在了:
- 您以某种方式导致渲染(更改状态,或父级重新渲染)
- React 渲染你的组件(调用它)
- useLayoutEffect 运行,React 等待它完成。
- 屏幕在视觉上更新
useLayoutEffect() => 渲染 => dom 突变 [分离] => useLayoutEffec() [访问 dom 新状态](改变 dom)=> 重绘(提交,附加)
===> 意思useLayoutEffect()
像跑getSnapshotBeforeUpdate()
知道这个!我们可以创建我们的自定义钩子,允许我们使用getSnapshotBeforeUpdate()
和做类似的事情didComponentUpdate()
。
这样的例子是更新聊天应用程序中自动更新的滚动!
使用PreviousPropsAndState()
类似于usePrevious()
“如何获取先前的props和状态”中提到的钩子
这里有一个钩子实现,用于保存和获取以前的props和状态!
const usePrevPropsAndState = (props, state) => {
const prevPropsAndStateRef = useRef({ props: null, state: null })
const prevProps = prevPropsAndStateRef.current.props
const prevState = prevPropsAndStateRef.current.state
useEffect(() => {
prevPropsAndStateRef.current = { props, state }
})
return { prevProps, prevState }
}
我们可以看到我们需要如何传递 props 和 state 对象!
你通过的就是你得到的!所以很容易使用!一个对象会做得很好!
useGetSnapshotBeforeUpdate 和 useComponentDidUpdate
这里是整体解决方案或实施
const useGetSnapshotBeforeUpdate = (cb, props, state) => {
// get prev props and state
const { prevProps, prevState } = usePrevPropsAndState(props, state)
const snapshot = useRef(null)
// getSnapshotBeforeUpdate (execute before the changes are comitted for painting! Before anythingg show on screen) - not run on mount + run on every update
const componentJustMounted = useRef(true)
useLayoutEffect(() => {
if (!componentJustMounted.current) { // skip first run at mount
snapshot.current = cb(prevProps, prevState)
}
componentJustMounted.current = false
})
// ________ a hook construction within a hook with closure __________
const useComponentDidUpdate = cb => {
// run after the changes are applied (commited) and apparent on screen
useEffect(() => {
if (!componentJustMounted.current) { // skip first run at mount
cb(prevProps, prevState, snapshot.current)
}
})
}
// returning the ComponentDidUpdate hook!
return useComponentDidUpdate
}
您可以注意到我们如何在另一个钩子中构造钩子!利用闭包!并直接访问元素!并连接两个钩子!
预提交阶段和提交阶段(和效果挂钩)
我用过那些术语!它实际上是什么意思?
课堂示例
从文档
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
我们的自定义挂钩等效
const App = props => {
// other stuff ...
const useComponentDidUpdate = useGetSnapshotBeforeUpdate(
(prevProps, prevState) => {
if (prevProps.list.length < props.list.length) {
const list = listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
},
props,
state
)
useComponentDidUpdate((prevProps, prevState, snapshot) => {
if (snapshot !== null) {
const list = listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
})
// rest ...
}
钩子中的 useEffectLayout()useGetSnapshotBeforeUpdate
将首先执行!
useEffect() in 之后useComponentDidUpdate
会执行!
正如生命周期模式中所示!