Рассматривая ниже пример крючков
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>
}
}
Но мой вопрос: как я могу заставить вышеуказанный функциональный компонент немедленно перерисовать с помощью хуков?
Последняя строка в setCount усечена. Непонятно, какова цель setCount в его текущем состоянии.
Это просто действие после this.forceUpdate (); Я добавил это просто, чтобы объяснить this.forceUpdate () в моем вопросе
Как бы то ни было: я боролся с этим, потому что думал, что мне нужен ручной повторный рендеринг, и, наконец, понял, что мне просто нужно переместить внешнюю переменную в ловушку состояния и использовать функцию настройки, которая исправила все мои проблемы без повторный рендер. Не сказать, что это никогда необходимо, но стоит взглянуть третий и четвертый взгляд, чтобы увидеть, нужен ли он на самом деле в вашем конкретном случае использования.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Желательно, чтобы ваш компонент зависел только от состояния и свойств, и он будет работать так, как ожидалось, но если вам действительно нужна функция для принудительного повторного рендеринга компонента, вы можете использовать ловушку 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>Хорошо, но почему React представил this.forceUpdate (); в первую очередь, когда компонент повторно отрисовывается с помощью setState в более ранних версиях?
@ Think-Twice Я лично никогда не использовал его, и сейчас я не могу придумать для него хороший вариант использования, но я думаю, что это спасательный люк для тех действительно особых случаев использования. «Обычно вам следует избегать любого использования forceUpdate() и читать только с this.props и this.state в render()».
Согласовано. Я даже никогда не использовал это по своему опыту, но я знаю, как это работает, поэтому просто пытаюсь понять, как то же самое можно сделать в хуках.
@Tholle and I can't think of a good use case for it right now У меня есть один, как насчет того, чтобы состояние не контролировалось React. Я не использую Redux, но предполагаю, что он должен выполнить какое-то принудительное обновление. Я лично использую прокси для поддержания состояния, затем компонент может проверять наличие изменений свойств, а затем обновлять. Кажется, тоже работает очень эффективно. Например, все мои компоненты затем контролируются прокси, этот прокси поддерживается SessionStorage, поэтому даже если пользователь обновляет свою веб-страницу, состояние даже выпадающих списков и т. д. Сохраняется. IOW: Я вообще не использую состояние, все управляется с помощью props.
Это возможно с useState или useReducer, поскольку useState использует useReducer внутри:
const [, updateState] = React.useState();
const forceUpdate = React.useCallback(() => updateState({}), []);
forceUpdate не предназначен для использования в нормальных условиях, только для тестирования или других неурегулированных случаев. Эту ситуацию можно разрешить более традиционным способом.
setCount является примером неправильно используемого forceUpdate, setState является асинхронным по соображениям производительности и не должен быть синхронным только потому, что обновления состояния не были выполнены правильно. Если состояние зависит от ранее установленного состояния, это должно быть выполнено с помощью функция обновления,
If you need to set the state based on the previous state, read about the updater argument below.
<...>
Both state and props received by the updater function are guaranteed to be up-to-date. The output of the updater is shallowly merged with state.
setCount не может быть иллюстративным примером, потому что его цель неясна, но это относится к функции обновления:
setCount(){
this.setState(({count}) => ({ count: count + 1 }));
this.setState(({count2}) => ({ count2: count + 1 }));
this.setState(({count}) => ({ count2: count + 1 }));
}
Это переводится как 1: 1 в хуки, за исключением того, что функции, которые используются как обратные вызовы, лучше запоминать:
const [state, setState] = useState({ count: 0, count2: 100 });
const setCount = useCallback(() => {
setState(({count}) => ({ count: count + 1 }));
setState(({count2}) => ({ count2: count + 1 }));
setState(({count}) => ({ count2: count + 1 }));
}, []);
Как работает const forceUpdate = useCallback(() => updateState({}), []);? Это вообще принудительное обновление?
@ DávidMolnár useCallback запоминает forceUpdate, поэтому он остается неизменным в течение срока службы компонентов и может безопасно передаваться в качестве опоры. updateState({}) обновляет состояние новым объектом при каждом вызове forceUpdate, это приводит к повторному рендерингу. Так что да, он вызывает обновление при вызове.
Так что деталь useCallback на самом деле не нужна. Он должен нормально работать и без него.
А с updateState(0) это не сработает, потому что 0 - это примитивный тип данных? Должен ли это быть объект или updateState([]) (то есть использование с массивом) тоже подойдет?
@Andru Да, состояние обновляется один раз, потому что 0 === 0. Да, массив будет работать, потому что это тоже объект. Можно использовать все, что не проходит проверку на равенство, например updateState(Math.random()) или счетчик.
@EstusFlask На самом деле сравнение выполняется с использованием Object.is - который отличается, поскольку Number.NaN равен самому себе для сравнения идентичности (и аналогично +0 и -0 различны).
Одно незначительное различие между setState и forceUpdate состоит в том, что forceUpdate пропускает вызов shouldComponentUpdate. А вот с хуками нет возможности пропустить React.memo.
Как уже упоминали другие, useState работает - вот как mobx-react-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
Насколько я понимаю из хуков, это может не сработать, поскольку useForceUpdate будет возвращать новую функцию каждый раз, когда функция перерисовывается. Чтобы forceUpdate работал при использовании в useEffect, он должен возвращать useCallback(update) См. kentcdodds.com/blog/usememo-and-usecallback
Спасибо, @MartinRatinaud - да, это могло вызвать утечку памяти без использования useCallback (?) - исправлено.
Вы можете просто определить useState следующим образом:
const [, forceUpdate] = React.useState(0);
И использование: forceUpdate(n => !n)
Надеюсь на эту помощь!
Не удастся, если forceUpdate вызывается четное количество раз за рендеринг.
Просто продолжайте увеличивать значение.
Это подвержено ошибкам и должно быть удалено или отредактировано.
Вы можете (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>
)
В этом случае, когда setBoolean изменяет дважды, дочерние элементы React.useEffect могут не распознать обновление.
Насколько я понимаю, React рассматривает каждое логическое обновление как причину для повторного рендеринга страницы, даже если логическое значение быстро переключается обратно. Тем не менее, React, конечно, не является стандартом, и то, как он работает в этом случае, не определено и может быть изменено.
Я не знаю. Почему-то меня привлекает эта версия. Меня это щекочет :-). Кроме того, он чистый. Что-то изменилось, от чего зависит мой JSX, поэтому я перерисовываю. Его невидимость не умаляет ИМО.
Возможный вариант - принудительное обновление только определенного компонента с помощью key. Обновление ключа запускает рендеринг компонента (который раньше не обновлялся)
Например:
const [tableKey, setTableKey] = useState(1);
...
useEffect(() => {
...
setTableKey(tableKey + 1);
}, [tableData]);
...
<DataTable
key = {tableKey}
data = {tableData}/>
Часто это самый чистый способ, если существует соотношение 1: 1 между значением состояния и требованием повторной визуализации.
это простое решение
Для обычных компонентов на основе React Class см. React Docs для API forceUpdate по URL-адресу это. В документах упоминается, что:
Normally you should try to avoid all uses of forceUpdate() and only read from this.props and this.state in render()
Однако в документации также упоминается, что:
If your render() method depends on some other data, you can tell React that the component needs re-rendering by calling forceUpdate().
Итак, хотя варианты использования forceUpdate могут быть редкими, и я никогда его не использовал, тем не менее я видел, как его использовали другие разработчики в некоторых устаревших корпоративных проектах, над которыми я работал.
Итак, для эквивалентной функциональности для функциональных компонентов обратитесь к React Docs for 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.info("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-адресу это.
Альтернатива ответу @MinhKha:
useReducer может быть намного чище:
const [, forceUpdate] = useReducer(x => x + 1, 0);
Использование:
forceUpdate() - очиститель без параметров
Как правило, можно использовать любой подход к обработке состояния, при котором требуется запускать обновление.
const forceUpdate: () => void = React.useState()[1].bind(null, {}) // see NOTE below
const forceUpdate = React.useReducer(() => ({}), {})[1] as () => void
Просто оберните любой подход, который вы предпочитаете, вот так
function useForceUpdate(): () => void {
return React.useReducer(() => ({}), {})[1] as () => void // <- paste here
}
«Для запуска обновления» означает сообщить движку React, что какое-то значение изменилось и что он должен повторно отрендерить ваш компонент.
[, setState] от useState() требует параметра. Избавляемся от него привязав свежий объект {}.() => ({}) в useReducer - это фиктивный редуктор, который возвращает новый объект каждый раз при отправке действия.
{}(свежий объект) требуется, чтобы запускать обновление, изменяя ссылку в состоянии.
PS: useState просто оборачивает useReducer внутри. источник
ЗАМЕТКА: Использование .bind с useState вызывает изменение ссылки на функцию между отрисовками. Можно обернуть его внутри useCallback как уже объяснено здесь, но тогда это не будет сексуальная однострочник ™. Версия Reducer уже держит ссылается на равенство между рендерами. Это важно, если вы хотите передать функцию forceUpdate в props.
const forceUpdate = React.useState()[1].bind(null, {}) // see NOTE above
const forceUpdate = React.useReducer(() => ({}))[1]
Разве это не приведет к тому, что хуки будут вызываться разное количество раз между рендерами, если forceUpdate вызывается условно, что нарушит правила хуков и потенциально оставит хуки для доступа к неправильным данным?
@ user56reinstatemonica8 По сути, это просто назначение состояния, которое запускает рендеринг, ничего необычного.
Мой вариант forceUpdate не через counter, а через объект:
// Emulates `forceUpdate()`
const [unusedState, setUnusedState] = useState()
const forceUpdate = useCallback(() => setUnusedState({}), [])
Потому что {} !== {} каждый раз.
Что такое useCallback()? Откуда это пришло? ой. Теперь я это вижу ...
Официальное решение Часто задаваемые вопросы по React Hooks для forceUpdate:
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
// usage
<button onClick = {forceUpdate}>Force update</button>
const App = () => {
const [_, forceUpdate] = useReducer((x) => x + 1, 0);
return (
<div>
<button onClick = {forceUpdate}>Force update</button>
<p>Forced update {_} times</p>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));<script src = "https://cdnjs.cloudflare.com/ajax/libs/react/16.10.1/umd/react.production.min.js" integrity = "sha256-vMEjoeSlzpWvres5mDlxmSKxx6jAmDNY4zCt712YCI0 = " crossorigin = "anonymous"></script>
<script src = "https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.1/umd/react-dom.production.min.js" integrity = "sha256-QQt6MpTdAD0DiPLhqhzVyPs1flIdstR4/R7x4GqCvZ4 = " crossorigin = "anonymous"></script>
<script>var useReducer = React.useReducer</script>
<div id = "root"></div>Это будет отображать зависимые компоненты 3 раза (массивы с одинаковыми элементами не равны):
const [msg, setMsg] = useState([""])
setMsg(["test"])
setMsg(["test"])
setMsg(["test"])
Я считаю, что вам даже не нужно помещать элемент в массив. Пустой массив не может быть строго равен другому пустому массиву просто по другой ссылке, как и объекты.
да, просто хотел показать это как способ передачи данных
Решение в одной строке:
const [,forceRender] = useReducer((s) => s+1, 0)
Вы можете узнать об useReducer здесь. https://reactjs.org/docs/hooks-reference.html#usereducer
Простой код
const forceUpdate = React.useReducer(bool => !bool)[1];
Использовать:
forceUpdate();
Однострочное решение:
const useForceUpdate = () => useState()[1];
useState возвращает пару значений: текущее состояние и функция, которая его обновляет - штат и сеттер, здесь мы используем только сеттер для принудительного повторного рендеринга.
У react-tidy есть специальный хук только для этого под названием useRefresh:
import React from 'react'
import {useRefresh} from 'react-tidy'
function App() {
const refresh = useRefresh()
return (
<p>
The time is {new Date()} <button onClick = {refresh}>Refresh</button>
</p>
)
}
Отказ от ответственности Я автор этой библиотеки.
Есть много способов принудительного повторного рендеринга в Hook.
Для меня простой способ с useState() и подсказкой значений эталонного объекта.
const [, forceRender] = useState({});
// Anywhre
forceRender({});
const useForceRender = () => {
const [, forceRender] = useReducer(x => !x, true)
return forceRender
}
использование
function Component () {
const forceRender = useForceRender()
useEffect(() => {
// ...
forceRender()
}, [])
Можете ли вы опубликовать версию вашего оригинального компонента, использующего
this.forceUpdate()? Может быть, есть способ добиться того же и без этого.