React 回忆录,四React 中的状态管理

Hi 各位,欢迎来到 React 回忆录!???? 在上一章中,我介绍了使用 React 渲染界面元素的方法,以及在这个过程中蕴含的“组件化”想想。在本章中,我们将把目光聚焦于 React 组件内部的状态管理,去认识或重新思考以下三个核心概念:

  1. propsstate
  2. 函数组件
  3. 类组件

让我们开始吧! ????

01. React 中的数据

站在“组件”的角度上,React 把应用中流动的数据分为两种类型:

  1. 不可更改内容,但可以单向跨组件传递的 props
  2. 可以更改内容,但不能跨组件传递的 state

进一步说,propsstate 的区别在于,props 是外部的,并且被任何渲染这个组件的代码所控制。而 state 则是内部的,并且被组件自身所控制。

非计算机专业的初学者经常困惑 propsstate 在名称与含义上的关联,其实大可不必在意,他们本质上只是数据的别称,只是在 React 中,它们被各自赋予了特殊的限制或能力。

你可以通过组件上的 props 属性,像在 HTML 中传递属性一样,将你想要传递的任何数据传递给子组件,所有的属性都会被存储在子组件(类组件)的 this.props 对象中。

function Parent(props) {
    return <Child name={"Tom"} />
}

function Child(props) {
    return <span>(props.name)</span>
}

02. 函数组件

我们之前提到过,React 使用组件渲染视图提升性能,而组件即是一个函数,可以用一个公式来简洁的表示其功用:f(数据) => UI。到这里我想你应该注意到了,为什么我们说 React 并不是一个大型的 MVC (或 MVVM)框架,因为 React 只负责视图层(View)的渲染,其他的事情将由 React 生态中的其他工具来完成。

话说回来,对于 React 组件而言,最简单的一种形式莫过于函数组件了,它充分展现了 React 的哲学,一次只做一件事组件化数据驱动UI

函数组件又称为“无状态组件”,“受控组件”或“木偶组件”,因为函数组件只负责接收 props 并返回 UI,它自身并不能拥有可改变的数据,在真实的 React 应用开发场景下,我们经常尽可能的使用函数组件,将整个应用的 UI 拆分成尽可能小的视觉单元。

这是因为函数组件是非常直观的,它接收属性返回元素,内部逻辑清晰明确,而且更重要的是,函数组件内没有 this 关键字,因此你永远不用担心烦人的“this上下文问题”。

记住:如果你的组件不需要追踪内部状态,尽量使用函数组件。


03. 类组件

和函数组件相对应的,便是“类组件”了,类似的,它也被称为“有状态组件”,“非受控组件”和“容器组件”。这里需要注意,虽然我们按照代码的形式为两种类型的组件命名,但这并不严谨,因为在 JavaScript 中,“类”也是函数。

不同于函数组件,类组件拥有着可以更改的内部数据 -- state。它最终影响着页面的渲染情况,而且 state 可以被组件在任何时刻在内部修改。通常的时刻时用户与界面发生交互的时候。

由于 React 把变化的数据封装在组件内部,并坚持单向数据流的原则。我们有了高度抽象的 UI 组件,并封装复杂的业务逻辑。这使得我们可以通过构建,组合一系列小组件开发出大型应用。

那么应该如何向类组件添加 state 呢?很简单,我们所要做的只是在类组件内部添加一个 state 属性,state 属性是一个对象。这个对象代表了组件的状态,对象的每一个属性名都代表组件的一个特定的状态,下面是具体的代码:

import React from "react"

class Parent extends React.Component {
    state = {
        name: "Eliot",
    }
    
    render() {
        return <p>{this.state.name}</p>
    }
}

React 使我们迫使大脑关注两个重要的部分:

  1. 组件看起来是什么样?
  2. 组件当前的状态是什么?

通过让组件管理自己的状态,任何时候状态的变更都会令 React 自动更新相应的页面部分。这便是使用 React 构建组件的主要优势之一:当页面需要重新渲染时,我们仅仅需要思考的是如何更改状态。我们不必跟踪页面的哪些部分需要更改,不需要决定如何有效的重新呈现页面,React 自会比较先前的输出和新的输出,决定什么应该发生改变,并为我们做出决定。而这个确定之前改变了什么和现在应该新输出什么的过程有一个专门的名词,叫做 Reconciliation


04. 修改 state

你应该还记得类组件与函数组件最大的不同在于类组件自身拥有可以改变内部数据的能力。那么如何行使这一能力呢?和直觉不同,要做到这一点,你需要使用 React 提供的专门的 API:this.setState()

你有两种方式使用该 API:

  1. 设置对象参数;
  2. 设置函数参数;

让我们先来看看第一种:

this.setState({
    name: "Tom"
})

React 会自动合并对 state 的改变。而有时,你的组件需要一个新的 state ,而这个 state 的变化又依赖于旧的 state 值,每当这种时候,你就该使用第二种 API 调用方式:

this.setState((prevState) => ({
    name: "mr." + prevState.name
}))

讲到这里你可能会感到奇怪,只是更新 state 而已,为什么还需要调用一个专门的 API?我们直接修改之前定义的 state 对象不就好了吗?之所以这样设计的原因是,组件内 state 的变化不仅仅是对象属性值发生变化那么简单,它还需要驱动整个 UI 进行重新渲染,因此 this.setState() 这个 API 被调用时实际上做了两件事:

  1. 修改 state 对象;
  2. 驱动组件重新渲染;

如果你对 React 有一定研究,你可能会质疑我以上所罗列的两点并不精确,的确如此,小小的 this.setState() API 其实内部还有很多细节值得注意,例如,当调用 this.setState() 时并不会立即改变 state 的值,也当然不会立即重新渲染组件。例如,当以对象为参数调用 this.setState() API 时,尽管内部重复为数据赋值,最终的数据也只保留最后一次更改的结果。

不过幸好,这些略显古怪的状态早有前人为我们做了详尽的解释,如果你感兴趣,请点击下方链接查询更多的信息:


05. 控制组件

当你在 Web 应用中使用表单时,这个表单的数据被存储于相应的 DOM 节点内部,但正如我们之前提到的,React 的整个关键点就在于如何高效的管理应用内的状态。所以虽然表单的数据被存储于 DOM 中,React 依然可以对它进行状态管理。

而管理的方式即是使用“控制组件”。简单而言,“控制组件”会渲染出一个表单,但是将表单所需的所有真实数据作为 state 存储于组件内部,而不是 DOM 中。

之所以被称为“控制组件”的原因也即在于此,“控制组件”控制着组件内的表单数据,因此,唯一更新表单数据的方式就是更新组件内部对应的 state 值。

import React as "react"

class Input extends React.Component {
    state = {
        value: "enter something...",
    }
    
    handleClick: (e) => {
        this.setState({value: e.target.value})
    }
    
    render() {
        <input value={this.state.value} onKeyup={this.handleClick} />
    }
}

可以看到,我们使用 handleClick 方法响应用户每一次键盘敲击以即时更新表单状态,这样做不仅天然的支持了即时的输入验证,还允许你有条件的禁止或点亮表单按钮。


06. 小结

这一章我们介绍了 React 的两种数据形式:stateprops,并且介绍了 React 组件的两种形式:函数组件类组件,希望格外有所收获,如果有任何问题或建议,也欢迎各位在评论区内留言,下一章见 ????

PS:????如果你对该专题感兴趣,别忘了订阅本专栏,确保及时收到更新通知。记得点击下方????的各个按钮,让我知道你认可我的付出,这是激励我持续产出的动力和源泉 ????。