Как заставить компонент перерисовать с помощью хуков в React?
Рассматривая ниже пример хуков
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
По сути, мы используем метод this.forceUpdate(), чтобы заставить компонент немедленно повторно выполнить рендеринг в компонентах класса React, как показано в примере ниже.
class Test extends Component{
constructor(props){
super(props);
this.state = {
count:0,
count2: 100
}
this.setCount = this.setCount.bind(this);//how can I do this with hooks in functional component
}
setCount(){
let count = this.state.count;
count = count+1;
let count2 = this.state.count2;
count2 = count2+1;
this.setState({count});
this.forceUpdate();
//before below setState the component will re-render immediately when this.forceUpdate() is called
this.setState({count2: count
}
render(){
return (<div>
<span>Count: {this.state.count}></span>.
<button onClick={this.setCount}></button>
</div>
}
}
Но мой вопрос: Как я могу заставить вышеуказанный функциональный компонент немедленно рендериться с помощью ловушек?
Ответы
Ответ 1
Это возможно с useState
или useReducer
, поскольку useState
использует useReducer
для внутреннего использования:
const [, updateState] = React.useState();
const forceUpdate = useCallback(() => updateState({}), []);
forceUpdate
не предназначен для использования при обычных обстоятельствах, только при тестировании или в других неурегулированных случаях. Эту ситуацию можно разрешить более общепринятым способом.
setCount
является примером неправильно используемого forceUpdate
. setState
является асинхронным по соображениям производительности и не должен быть вынужден быть синхронным только потому, что обновления состояний не были выполнены правильно. Если состояние опирается на ранее установленное состояние, это следует сделать с помощью функции обновления,
Если вам нужно установить состояние на основе предыдущего состояния, прочтите об аргументе средства обновления ниже.
<...>
Состояние и реквизиты, полученные функцией обновления, гарантированы быть в курсе. Вывод средства обновления поверхностно объединен с состояние.
setCount
может не быть иллюстративным примером, потому что его назначение неясно, но это относится к функции обновления:
setCount(){
this.setState(({count}) => ({ count: count + 1 }));
this.setState(({count2}) => ({ count2: count + 1 }));
this.setState(({count}) => ({ count2: count + 1 }));
}
Ответ 2
Как уже упоминали другие, useState
работает - вот как mobx-реагировать-Lite реализует обновления - вы можете сделать что-то подобное.
Определите новый крюк, useForceUpdate
-
import { useState, useCallback } from 'react'
export function useForceUpdate() {
const [, setTick] = useState(0);
const update = useCallback(() => {
setTick(tick => tick + 1);
}, [])
return update;
}
и использовать его в компоненте -
const forceUpdate = useForceUpdate();
if (...) {
forceUpdate(); // force re-render
}
смотрите https://github.com/mobxjs/mobx-react-lite/blob/master/src/utils.ts и https://github.com/mobxjs/mobx-react-lite/blob/master/src/useObserver.ts
Ответ 3
Желательно, чтобы ваш компонент зависел только от состояния и реквизита, и он будет работать как положено, но если вам действительно нужна функция для принудительного повторного рендеринга компонента, вы можете использовать useState
и вызывать функцию при необходимости.
пример
const { useState, useEffect } = React;
function Foo() {
const [, forceUpdate] = useState();
useEffect(() => {
setTimeout(forceUpdate, 2000);
}, []);
return <div>{Date.now()}</div>;
}
ReactDOM.render(<Foo />, document.getElementById("root"));
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"></script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Ответ 4
Вы можете просто определить useState следующим образом:
const [, forceUpdate] = React.useState(0);
И использование: forceUpdate(n => !n)
Надеюсь, это поможет!
Ответ 5
Вы можете (ab) использовать обычные перехватчики для принудительного повторного рендеринга, воспользовавшись тем, что React не печатает логические значения в коде JSX.
// create a hook
const [forceRerender, setForceRerender] = React.useState(true);
// ...put this line where you want to force a rerender
setForceRerender(!forceRerender);
// ...make sure that {forceRerender} is "visible" in your js code
// ({forceRerender} will not actually be visible since booleans are
// not printed, but updating its value will nonetheless force a
// rerender)
return (
<div>{forceRerender}</div>
)
Ответ 6
Потенциальная опция - принудительно обновлять только определенный компонент, используя key
. Обновление ключа запускает рендеринг компонента (который раньше не обновлялся)
Например:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key={tableKey}
data={tableData}/>
Ответ 7
Для обычных компонентов на основе класса React обратитесь к документам React для API forceUpdate
по этому URL-адресу. В документах упоминается, что:
Обычно вы должны стараться избегать любого использования forceUpdate() и только читать из this.props и this.state в render()
Тем не менее, в документах также упоминается, что:
Если ваш метод render() зависит от некоторых других данных, вы можете указать React что компонент нуждается в повторном рендеринге, вызывая forceUpdate().
Итак, хотя варианты использования forceUpdate
могут быть редкими, и я никогда не использовал его, однако я видел, что его использовали другие разработчики в некоторых старых корпоративных проектах, над которыми я работал.
Таким образом, для эквивалентной функциональности для функциональных компонентов обратитесь к документам React для HOOKS по этому URL-адресу. По указанному выше URL-адресу можно использовать хук "useReducer", чтобы обеспечить функциональность forceUpdate
для функциональных компонентов.
Ниже приведен пример рабочего кода that does not use state or props
, который также доступен в CodeSandbox по этому URL-адресу.
import React, { useReducer, useRef } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
function App() {
// Use the useRef hook to store a mutable value inside a functional component for the counter
let countref = useRef(0);
const [, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
countref.current++;
console.log("Count = ", countref.current);
forceUpdate(); // If you comment this out, the date and count in the screen will not be updated
}
return (
<div className="App">
<h1> {new Date().toLocaleString()} </h1>
<h2>You clicked {countref.current} times</h2>
<button
onClick={() => {
handleClick();
}}
>
ClickToUpdateDateAndCount
</button>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
ПРИМЕЧАНИЕ. Альтернативный подход с использованием ловушки useState (вместо useReducer) также доступен по этому URL-адресу.
Ответ 8
Альтернатива ответу @MinhKha:
Это может быть намного чище с useReducer
:
const [, forceUpdate] = useReducer(x => x + 1, 0);
Использование:
forceUpdate()
- очиститель без параметров