首页 > 解决方案 > 了解 React 如何/为什么/何时更新 DOM 以及如何使用它

问题描述

我正在制作一个应用程序,其中有 3 个按钮,其中 2 个对我的问题很重要。一个按钮应该向用户正在查看的网站添加元素,另一个应该删除元素。

我是 React 的新手,感觉我掌握了基础知识,但显然需要更好地理解它。

我的第一个直觉是创建一个数组,我可以将元素推入其中,然后将该数组作为它的内容放在我的一个 JSX.Elements 中。

看起来像这样:

我的 JSX.Element:

   answerView = (
                <div>
                    <button onClick={this.removeIceCream}>Poista jäätelö!</button>
                    <button onClick={this.addIceCream}>Lisää jäätelö!</button>
                    <button onClick={this.keyInput}>OK</button>
                    <div>{this.iceCreamCount}</div>
                </div>
            );

仅向元素添加内容的功能。

addIceCream() {
   this.iceCreamCount.push(<li>"Jäätelö"</li>);
}

然而,这最终会在数组 iceCreamCount 中更新,但不会显示在 DOM 中。React 不会对元素的变化做出“反应”,而只会对状态或属性的变化做出反应吗?

这是否意味着为了根据用户输入操作 DOM 元素,包含元素的数组也需要是状态变量?


如果上述确实是不可能的,那么我想帮助我如何在 React 中正确地做到这一点,下面是我这样做的尝试。

我检查了一些教程和示例,并想出了这样的东西:

  addIceCream = () => {
        this.setState(state => {
            const iceCreams = state.iceCreams.concat(state.iceCream);

            return {
                iceCreams,
                iceCream: 'Jäätelö',
            };
        });
    };

我的状态声明如下所示:

this.state = { doorView: true, bunnyView: false, answerView: false, iceCream: "Jäätelö", iceCreams: [] };

在写这个问题的过程中,我实际上得到了这个看似有效的方法,但我也想了解它是如何以及为什么会起作用的,因为我对自己的知识并不十分自信。

但是我不觉得我已经掌握了这里到底发生了什么。我对箭头函数不是很有信心(如果有人有关于它们的简单教程,请做链接),虽然我认为我已经收集到了这一点,因为数据在 React 中应该是不可变的,这就是我需要用来 concat()创建的原因一个新数组,它是旧数组的副本 + 我添加的元素。但是为什么我需要return()状态变量以及为什么会有这么多箭头函数被抛出(见下文我的浓缩“我想知道的列表”)?


我想知道的

标签: javascriptreactjstypescript

解决方案


我将在下面回答问题。


回答第一个问题

我的问题的第一部分在 React 中是一种可行的方法,还是完全是错误的方法?如果可以做到,我将不胜感激,如果有人能告诉我我的想法是如何以及我的错误是什么。

不幸的是,第一种方法是“完全错误的方法”(在 React 世界中)。

addIceCream() {
   this.iceCreamCount.push(<li>"Jäätelö"</li>);
}

然而,这最终会在数组 iceCreamCount 中更新,但不会显示在 DOM 中。React 不会对元素的变化做出“反应”,而只会对状态或属性的变化做出反应吗?

就是这样。React 对React 跟踪的 state & props 变化做出“反应” 。

这是否意味着为了根据用户输入操作 DOM 元素,包含元素的数组也需要是状态变量?

这取决于。一些控件是“受控的”,另一个是“不受控的”(主要适用于表单字段。)

大多数时候,您会使用(React)“受控”选项来保持与 React 关联的所有状态以进行跟踪。(在this.state ={...}使用钩子的类组件或函数组件中,const [state, setState] = React.useState(...)

React 的Reconciliation(一种奇特的说法,知道发生了什么变化以及要呈现什么)算法通过检查更改的状态/道具“引用”(而不是值)来工作。

当你这样做时this.iceCreamCount.push(<li>"Jäätelö"</li>),你基本上是在改变一个数组的值,“this.iceCreamCount”,而不是它的引用。

而且在重新分配this.iceCreamCount给一个新对象之后,你还需要this.setState({iceCreamCount: newIceCreamCount})通知 React 有些东西已经改变并且需要重新渲染。

现在我们知道了为什么第一种方法不起作用,让我们继续您的解决方法。


回答第二个问题

我的问题的第二部分为什么以及如何工作?如果有人有时间和耐心从以下开始逐行阅读: addIceCream = () => {.... 并告诉我发生了什么,这将对我的理解和学习有很大帮助。

state declaration

this.state = {
  doorView: true,
  bunnyView: false,
  answerView: false,
  iceCream: "Jäätelö",
  iceCreams: []
};
addIceCream = () => {
  this.setState(state => {
    const iceCreams = state.iceCreams.concat(state.iceCream);

    return { iceCreams, iceCream: "Jäätelö" }
    };
  });
};

当您查看 时addIceCream,您确实在创建一个新的参考,iceCreams
正如上面问题 #1 中所解释的,这就是 React 的协调算法如何知道状态已更改的方式。
要点是 React 经过优化以检查引用更改,因为深度属性检查(假设this.state具有深度嵌套的对象,然后比较每个值将花费太长时间)

最后你会返回一个新的引用return { iceCreams, iceCream: "Jäätelö" },它会通知 React 有些事情确实发生了变化。


跟进评论。

这里的箭头函数是如何工作的,为什么“addIceCream”函数需要它们?

您已addIceCream使用箭头函数语法声明。
正如下面一节中提到的,没有分隔 this,箭头函数不会创建自己的this变量。

但是当你这样做时,由于范围规则的魔力,this它被设置为父上下文。this

addIceCream = () => {
  this.setState(state => {
    const iceCreams = state.iceCreams.concat(state.iceCream);

    return { iceCreams, iceCream: "Jäätelö" }
    };
  });
};

这就是为什么你可以this.setState在上面调用。JavaScript 引擎将尝试this在作用域链中找到最近的父级。

如果您已声明addIceCream为普通方法,

class App extends React.Component {
  addIceCream() { ...}
}

thenthis指向addIceCream因此,将无法访问App's this.setState,它React.Component在您扩展它时可用。

因此,您会看到一种解决方法,通过将自己的方法传递给它来绑定this类方法,这会使用当前类的this.

class App extends React.Component {
  constructor(props) {
    super(props)

    //                     ... this create a new method with `App`'s `this`.
    this.addIceCream = this.addIceCream.bind(this)
  }

  addIceCream() { 
    // now that `this` is bound to `App`'s `this`, you can call this
    this.setState({...})
  }
}

这在您使用类组件 (CC) 时适用。当您使用功能组件 (FC) 时,这无关紧要。

您可以使用 React 的新 Hook(useStateuseReducer)来保存状态。

用于useReducer复杂的状态管理

cosnt initialState = {
  doorView: true,
  bunnyView: false,
  answerView: false,
  iceCream: "Jäätelö",
  iceCreams: []
};

function reducer(state, action) {
  switch (action.type) {
    case 'ADD_ICECREAM':
      const { iceCream } = action.payload
      const iceCreams= [...state.iceCreams].concat(iceCream)
      return { ...state, iceCream, iceCreams }
    default: return state;
  }
}

function App() {
  const [icecreams, setIceCreams] = React.reducer(reducer, initialState);
  // You can use either one now.
  const addIceCream = iceCream =>
    dispatch({type: 'ADD_ICECREAM', payload: { iceCream }});
  // OR
  function addIceCream(iceCream) {
    dispatch({type: 'ADD_ICECREAM', payload: { iceCream }});
  }
}

reducer函数返回一个新的对象引用,因此会导致 React 重新渲染。

您可能已经注意到箭头语法版本使用constlike const addIceCream = iceCream => ...

现在它变成了一个指向函数的指针,因此它应该在使用之前声明,与function版本不同,因为JavaScript 提升是如何工作的。


推荐阅读