如何使用React钩子实现getSnapshotBeforeUpdate给我的相同逻辑?
答案 0 :(得分:1)
根据React Hooks FAQ,还没有一种方法可以实现带有钩子的getSnapshotBeforeUpdate
和ComponentDidCatch
生命周期方法
挂钩涵盖了类的所有用例吗?
我们的目标是让Hooks尽快涵盖类的所有用例 可能。没有钩子等同于罕见
getSnapshotBeforeUpdate
和componentDidCatch
生命周期还没有,但是我们 计划很快添加它们。对于Hooks来说还为时过早,因此一些集成如DevTools 支持或Flow / TypeScript键入可能尚未准备好。一些 第三方库也可能与Hooks不兼容 片刻。
答案 1 :(得分:1)
我们无法在任何钩子中获取快照数据(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>
);
};
我们还可以使用备忘录在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
中显示的相同操作答案 2 :(得分:0)
您可以使用useMemo()
代替getSnapshotBeforeUpdate()
。在此处详细了解how to memoize calculations with 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
答案 3 :(得分:0)
简短回答:它没有反应钩子!但是我们可以创建一个自定义的!
那是使用 useEffect()
和 useLayoutEffect()
!因为它们是关键元素!
最后一个例子是最后一个!所以一定要检查它(我们的自定义钩子等效)。
useEffect => useEffect 异步运行,并在渲染绘制到屏幕后运行。
useEffect() => render() => dom 突变 => 重绘 => useEffect() [访问 dom 新状态](直接改变 dom)=> 重绘
==> 含义 useEffect() 就像 comonentDidUpdate()
!
useLayoutEffect => 另一方面,useLayoutEffect 在渲染之后但在屏幕更新之前同步运行。就是这样:
useLayoutEffect() => 渲染 => dom 突变 [分离] => useLayoutEffec() [访问 dom 新状态](改变 dom)=> 重绘(提交,附加)
===> 含义 useLayoutEffect()
像 getSnapshotBeforeUpdate()
一样运行
知道了!我们可以创建我们的自定义钩子,允许我们使用 getSnapshotBeforeUpdate()
和 didComponentUpdate()
来做类似的事情。
这样的例子是更新聊天应用程序中自动更新的滚动!
类似于“how to get previous prop and state”中提到的 usePrevious()
钩子
这里有一个钩子实现,用于保存和获取以前的道具和状态!
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 对象!
你通过的是什么!所以很容易使用!一个对象会做得很好!
这里是完整的解决方案或实施
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
}
您可以注意到我们如何在另一个钩子中构造钩子!利用闭包!并直接访问元素!并连接两个钩子!
我用过这些术语!它实际上意味着什么?
来自the doc
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 ...
}
useGetSnapshotBeforeUpdate
钩子中的 useEffectLayout() 将首先执行!
useComponentDidUpdate
中的 useEffect() 将在之后执行!
正如生命周期模式中所示!