Как я могу реализовать ту же логику, которую дает мне getSnapshotBeforeUpdate, используя перехватчики реакции?
getSnapshotBeforeUpdate с использованием перехватчиков реакции
Ответы (4)
Согласно часто задаваемым вопросам о хуках React, пока нет возможности реализовать методы жизненного цикла getSnapshotBeforeUpdate
и ComponentDidCatch
с хуками
Охватывают ли хуки все варианты использования классов?
Наша цель состоит в том, чтобы хуки как можно скорее охватили все варианты использования классов. Пока нет эквивалентов хуков для необычных жизненных циклов
getSnapshotBeforeUpdate
иcomponentDidCatch
, но мы планируем добавить их в ближайшее время.Для хуков еще очень рано, поэтому некоторые интеграции, такие как поддержка DevTools или типизация Flow / TypeScript, могут быть еще не готовы. Некоторые сторонние библиотеки на данный момент могут быть несовместимы с хуками.
Мы не можем получить данные моментального снимка ни в одном из хуков (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.
Краткий ответ: для этого нет ловушки реакции! Но мы можем создать индивидуальный!
Это использует useEffect()
и useLayoutEffect()
! Ведь они ключевые элементы!
Последний пример - последний! Поэтому обязательно проверьте его (Эквивалент наших пользовательских хуков).
useEffect () и useLayoutEffect ()
useEffect = ›useEffect выполняется асинхронно и после визуализации отображается на экране.
- Вы каким-то образом вызываете рендеринг (изменение состояния или родительский рендеринг)
- React отображает ваш компонент (называет его)
- Экран визуально обновлен
- ТОГДА запускается useEffect
useEffect () = ›render () =› dom mutation = ›repaint =› useEffect () [доступ к новому состоянию dom] (изменение dom напрямую) = ›repaint
== ›Значение useEffect () похоже на comonentDidUpdate()
!
useLayoutEffect = ›useLayoutEffect, с другой стороны, запускается синхронно после рендеринга, но до обновления экрана. Это идет:
- Вы каким-то образом вызываете рендеринг (изменение состояния или родительский рендеринг)
- React отображает ваш компонент (называет его)
- useLayoutEffect запускается, а React ожидает его завершения.
- Экран визуально обновлен
useLayoutEffect () = ›render =› dom mutation [detached] = ›useLayoutEffec () [доступ к новому состоянию dom] (mutate dom) =› перерисовать (зафиксировать, прикрепить)
=== ›Значение useLayoutEffect()
запускать как getSnapshotBeforeUpdate()
Зная это! Мы можем создать собственные хуки, которые позволят нам делать такие вещи, как getSnapshotBeforeUpdate()
и didComponentUpdate()
.
Таким примером может быть обновление прокрутки для автоматического обновления в приложениях чата!
usePreviousPropsAndState ()
Подобно хуку 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 }
}
Мы видим, как нам нужно передать реквизиты и объект состояния!
То, что вы проходите, - это то, что вы получаете! Так что с ним легко работать! Объект будет хорошо!
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 () в useComponentDidUpdate
будет выполняться после!
Как только что было показано на схеме жизненного цикла!
Вы можете использовать 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