Можете ли вы заставить компонент React перерисовываться без вызова setState?
У меня есть внешний (для компонента), наблюдаемый объект, который я хочу прослушивать на предмет изменений. Когда объект обновляется, он генерирует события изменения, а затем я хочу повторно визуализировать компонент при обнаружении любого изменения.
С верхнего уровня React.render
это было возможно, но внутри компонента это не работает (что имеет смысл, так как render
метод просто возвращает объект).
Вот пример кода:
export default class MyComponent extends React.Component {
handleButtonClick() {
this.render();
}
render() {
return (
<div>
{Math.random()}
<button onClick={this.handleButtonClick.bind(this)}>
Click me
</button>
</div>
)
}
}
Нажатие на кнопку внутри звонков this.render()
, но это не то, что на самом деле вызывает рендеринг (вы можете увидеть это в действии, потому что текст, созданный {Math.random()}
не меняется). Однако, если я просто позвоню this.setState()
вместо this.render()
работает нормально.
Поэтому я предполагаю, что мой вопрос таков : нужно ли компонентам React иметь состояние для повторного рендеринга? Есть ли способ заставить компонент обновляться по требованию без изменения состояния?
24 ответа
В вашем компоненте вы можете позвонить this.forceUpdate()
заставить рендеринга.
Документация: https://facebook.github.io/react/docs/react-component.html
forceUpdate
следует избегать, потому что это отклоняется от мышления React. Документы React приводят пример того, когда forceUpdate
может быть использован:
По умолчанию, когда состояние вашего компонента или реквизит меняются, ваш компонент будет повторно визуализироваться. Однако, если они изменяются неявно (например, данные глубоко внутри объекта изменяются без изменения самого объекта) или если ваш метод render() зависит от некоторых других данных, вы можете указать React, что ему нужно повторно запустить render(), вызвав forceUpdate().
Тем не менее, я хотел бы предложить идею, что даже с глубоко вложенными объектами, forceUpdate
не нужно При использовании неизменного источника данных отслеживание изменений становится дешевым; изменение всегда приведет к появлению нового объекта, поэтому нам нужно только проверить, изменилась ли ссылка на объект. Вы можете использовать библиотеку Immutable JS для реализации неизменяемых объектов данных в вашем приложении.
Обычно вы должны стараться избегать любого использования forceUpdate () и читать только из this.props и this.state в render(). Это делает ваш компонент "чистым", а ваше приложение - намного проще и эффективнее. https://facebook.github.io/react/docs/component-api.html
Изменение ключа элемента, который вы хотите перерисовать, будет работать. Установите ключевую опору для вашего элемента через состояние, а затем, когда вы хотите обновить установленное состояние, чтобы иметь новый ключ.
<Element key={this.state.key} />
Затем происходит изменение, и вы сбрасываете ключ
this.setState({ key: Math.random() });
Хочу отметить, что это заменит элемент, на котором меняется ключ. Примером того, где это может быть полезно, является наличие поля ввода файла, которое вы хотите сбросить после загрузки изображения.
Хотя истинный ответ на вопрос ОП будет forceUpdate()
Я нашел это решение полезным в разных ситуациях. Я также хочу отметить, что если вы используете forceUpdate
Вы можете пересмотреть свой код и посмотреть, есть ли другой способ сделать что-то.
В 2021 году это официальный способ принудительного обновления функционального компонента React.
const [, forceUpdate] = useReducer(x => x + 1, 0);
function handleClick() {
forceUpdate();
}
Я знаю, что OP предназначен для компонента класса. Но этот вопрос был задан в 2015 году, и теперь, когда доступны хуки, многие могут искать
forceUpdate
в функциональных компонентах. Это немного для них.
На самом деле, forceUpdate()
является единственным правильным решением, так как setState()
может не вызвать повторную визуализацию, если в логике реализована дополнительная логика shouldComponentUpdate()
или когда он просто возвращается false
,
forceUpdate()
призвание
forceUpdate()
вызоветrender()
быть вызванным на компоненте, пропускаяshouldComponentUpdate()
, Больше...
SetState()
setState()
всегда будет вызывать повторное рендеринг, если логика условного рендеринга не реализована вshouldComponentUpdate()
, Больше...
forceUpdate()
может быть вызван из вашего компонента this.forceUpdate()
Я избежал forceUpdate
выполняя следующие
НЕПРАВИЛЬНЫЙ ПУТЬ: не используйте индекс в качестве ключа
this.state.rows.map((item, index) =>
<MyComponent cell={item} key={index} />
)
ПРАВИЛЬНЫЙ СПОСОБ: используйте идентификатор данных в качестве ключа, это может быть некоторый гид и т. Д.
this.state.rows.map((item) =>
<MyComponent item={item} key={item.id} />
)
таким образом, улучшая код, ваш компонент будет УНИКАЛЬНЫМ и будет отображаться естественным образом.
Если вы хотите, чтобы взаимодействовали два компонента React, которые не связаны отношениями (parent-child), рекомендуется использовать Flux или аналогичные архитектуры.
То, что вы хотите сделать, это прослушать изменения в наблюдаемом хранилище компонентов, которое содержит модель и ее интерфейс, и сохранить данные, которые вызывают изменение рендеринга как state
в MyComponent
, Когда хранилище отправляет новые данные, вы меняете состояние вашего компонента, что автоматически запускает рендеринг.
Обычно вы должны стараться избегать использования forceUpdate()
, Из документации:
Обычно вы должны стараться избегать любого использования forceUpdate() и читать только из this.props и this.state в render(). Это делает ваше приложение намного проще и эффективнее
Ни одно государство не заботится о том, чтобы перерисовать
Я часто хранил свои данные в состоянии, когда начал использовать реакцию, думая, что должен сделать это для рендеринга. Это было очень неправильно. Оказывается, вы даже можете избегать таких сложных вещей, как Flux и Redux, даже если вы катите несколько простых неизменных магазинов.
КлассBaseView для наследования и обработки обновлений магазина
import React, { Component } from 'react';
import { View } from 'react-native';
export default class BaseView extends Component {
constructor(props) {
super(props)
this.state = {}
}
onChange = () => {
this.setState(this.state) // dumb easy: triggers render
}
componentWillMount = () => {
this.stores && this.stores.forEach(store => {
// each store has a common change event to subscribe to
store.on('change', this.onChange)
})
}
componentWillUnmount = () => {
this.stores && this.stores.forEach(store => {
store.off('change', this.onChange)
})
}
}
Как пользоваться
import React, { Component } from 'react'
import { View, Text } from 'react-native'
import BaseView from './BaseView'
import myStore from './myStore'
class MyView extends BaseView {
contructor(props) {
super(props)
this.stores = [myStore]
}
render() {
return (<View><Text onPress={() => {
myStore.update({ message: 'hi' + Date.now() })
}}>{myStore.get().message}</Text></View>)
}
}
Пример простого магазина
var ee = require('event-emitter')
export default class Store {
constructor() {
this._data = {}
this._eventEmitter = ee({})
}
get() {
return {...this._data} // immutable
}
update(newData) {
this._data = {..._this.data, ...newData}
this._eventEmitter.emit('change')
}
on(ev, fn) {
this._eventEmitter.on(ev, fn)
}
off(ev, fn) {
this._eventEmitter.off(ev, fn)
}
}
Сохранить экземпляр как синглтон
import Store from './Store'
const myStore = new Store()
export default myStore
Я думаю, что это устраняет все грубые рамки, необходимые для небольших приложений. Используйте избыточный для средних и больших, хотя в сочетании с Immutable.js.
Поэтому я предполагаю, что мой вопрос таков: нужно ли компонентам React иметь состояние для повторного рендеринга? Есть ли способ заставить компонент обновляться по требованию без изменения состояния?
Другие ответы пытались проиллюстрировать, как вы могли бы, но дело в том, что вы не должны. Даже хакерское решение о смене ключа не имеет смысла. Сила React заключается в отказе от ручного управления временем, когда что-то должно отображаться, и вместо этого просто в связи с тем, как что-то должно отображаться на входах. Затем поставьте поток входов.
Если вам нужно вручную выполнить повторный рендеринг, вы почти наверняка что-то делаете неправильно.
Для полноты вы также можете добиться этого в функциональных компонентах:
const [, updateState] = useState();
const forceUpdate = useCallback(() => updateState({}), []);
// ...
forceUpdate();
Или, как многоразовый крючок:
const useForceUpdate = () => {
const [, updateState] = useState();
return useCallback(() => updateState({}), []);
}
// const forceUpdate = useForceUpdate();
См.: /questions/38482667/kak-zastavit-komponent-pererisovat-s-pomoschyu-hukov-v-react/38482677#38482677
Обратите внимание, что использование механизма принудительного обновления по-прежнему является плохой практикой, поскольку это противоречит менталитету реакции, поэтому по возможности его следует избегать.
Есть несколько способов перерисовки вашего компонента:
Самое простое решение - использовать метод forceUpdate():
this.forceUpdate()
Еще одно решение - создать неиспользуемый ключ в состоянии (nonUsedKey) и вызвать функцию setState с обновлением этого nonUsedKey:
this.setState({ nonUsedKey: Date.now() } );
Или переписать все текущее состояние:
this.setState(this.state);
Изменение реквизита также обеспечивает повторное отображение компонента.
Мы можем использовать this.forceUpdate(), как показано ниже.
class MyComponent extends React.Component {
handleButtonClick = ()=>{
this.forceUpdate();
}
render() {
return (
<div>
{Math.random()}
<button onClick={this.handleButtonClick}>
Click me
</button>
</div>
)
}
}
ReactDOM.render(<MyComponent /> , mountNode);
Элемент "Math.random" элемента в DOM обновляется только в том случае, если вы используете setState для повторной визуализации компонента.
Все ответы здесь верны, дополняя вопрос для понимания... поскольку мы знаем, что перерисовать компонент без использования setState({}) можно с помощью forceUpdate().
Приведенный выше код работает с setState, как показано ниже.
class MyComponent extends React.Component {
handleButtonClick = ()=>{
this.setState({ });
}
render() {
return (
<div>
{Math.random()}
<button onClick={this.handleButtonClick}>
Click me
</button>
</div>
)
}
}
ReactDOM.render(<MyComponent /> , mountNode);
Вы можете сделать это несколькими способами:
1. Используйте forceUpdate()
метод:
При использовании forceUpdate()
метод. Одним из примеров является то, что он игнорирует shouldComponentUpdate()
метод и будет визуализировать представление независимо от того, shouldComponentUpdate()
возвращает ложь Из-за этого следует избегать использования forceUpdate(), когда это возможно.
2. Передача этого состояния в setState()
метод
Следующая строка кода устраняет проблему с предыдущим примером:
this.setState(this.state);
На самом деле все, что это делает, это перезаписывает текущее состояние текущим состоянием, которое запускает повторный рендеринг. Это все еще не обязательно лучший способ сделать что-то, но он преодолевает некоторые глюки, с которыми вы можете столкнуться, используя метод forceUpdate().
Force Update(), но каждый раз, когда я слышал, как кто-то говорит об этом, вам не следует использовать это.
Просто еще один ответ для подтверждения принятого ответа:-)
Реакт препятствует использованию forceUpdate()
потому что у них, как правило, очень функциональный подход "это единственный способ сделать это". Это хорошо во многих случаях, но многие разработчики React приходят с OO-фоном, и при таком подходе вполне нормально слушать наблюдаемый объект.
И если вы это сделаете, вы, вероятно, знаете, что вы ДОЛЖНЫ перерисовывать, когда наблюдаемые "пожары", и, как так, вы ДОЛЖНЫ использовать forceUpdate()
и это на самом деле плюс, что shouldComponentUpdate()
не участвует здесь.
Такие инструменты, как MobX, который использует OO-подход, фактически делают это под поверхностью (на самом деле MobX вызывает render()
напрямую)
Чтобы выполнить то, что вы описываете, попробуйте this.forceUpdate().
forceUpdate();
метод будет работать, но желательно использовать setState();
Я нашел, что лучше избегать forceUpdate(). Одним из способов принудительного повторного рендеринга является добавление зависимости render() от временной внешней переменной и изменение значения этой переменной по мере необходимости.
Вот пример кода:
class Example extends Component{
constructor(props){
this.state = {temp:0};
this.forceChange = this.forceChange.bind(this);
}
forceChange(){
this.setState(prevState => ({
temp: prevState.temp++
}));
}
render(){
return(
<div>{this.state.temp &&
<div>
... add code here ...
</div>}
</div>
)
}
}
Вызовите this.forceChange(), когда вам нужно выполнить принудительный повторный рендеринг.
Другой способ звонит setState
И сохранить состояние:
this.setState(prevState=>({...prevState}));
С использованиемforceUpdate
метод, вы можете принудительно обновить компонент, не меняя его состояния. Например:
export default class MyComponent extends React.Component {
handleButtonClick = () => {
this.forceUpdate(); // Force a re-render
}
}
Да, если вы визуализируете компоненты в массиве, вы можете использовать фильтр массива с уникальным идентификатором в каждом компоненте, чтобы получить определенный компонент и повторно отобразить массив компонентов.
ES6 - я включаю пример, который был полезен для меня:
В "коротком операторе if" вы можете передать пустую функцию следующим образом:
isReady ? ()=>{} : onClick
Это, кажется, самый короткий подход.
()=>{}
использовать в качестве смеси ,
вести себя как
useEffect(() => console.log('mounted'), []);
Первый аргумент — это обратный вызов, который будет запущен на основе второго аргумента, представляющего собой массив значений. Если какое-либо из значений этого второго аргумента изменилось, функция обратного вызова, которую вы определили внутри, будет запущена.
Однако в примере, который я показываю, я передаю пустой массив в качестве второго аргумента, и он никогда не будет изменен, поэтому функция обратного вызова будет вызываться один раз при монтировании компонента.
Такого рода обобщения. Если вместо пустого значения у вас есть аргумент, например:
useEffect(() => {
}, [props.lang]);
Это означает, что каждый раз, когда изменяется props.lang, будет вызываться ваша функция обратного вызова. На самом деле ваш компонент не будет перерисовываться, если только вы не управляете каким-то состоянием внутри этой функции обратного вызова, которая может запустить повторный рендеринг.
Если вы хотите запустить повторный рендеринг, ваша функция рендеринга должна иметь состояние, которое вы обновляете в своем
Например, здесь функция рендеринга начинается с отображения английского языка в качестве языка по умолчанию, и в моем эффекте использования я меняю этот язык через 3 секунды, поэтому рендеринг повторно отображается и начинает показывать «испанский».
function App() {
const [lang, setLang] = useState("english");
useEffect(() => {
setTimeout(() => {
setLang("spanish");
}, 3000);
}, []);
return (
<div className="App">
<h1>Lang:</h1>
<p>{lang}</p>
</div>
);
}
Вы можете использовать forceUpdate() для более подробной проверки (forceUpdate()).
брух, если меня не волнует сохранение локального состояния или каких-либо данных, я просто
location.reload()
, это перезагружает страницу, а также, очевидно, повторно отображает компонент