React 对state进行保留和重置

对 state 进行保留和重置

各个组件的 state 是各自独立的。根据组件在 UI 树中的位置,React 可以跟踪哪些 state 属于哪个组件。你可以控制在重新渲染过程中何时对 state 进行保留和重置。

开发环境:React+ts+antd

学习内容

  • React 何时选择保留或重置状态
  • 如何强制 React 重置组件的状态
  • 键和类型如何影响状态是否被保留

状态与渲染树中的位置相关

React 会为 UI 中的组件结构构建 渲染树。

当向一个组件添加状态时,那么可能会认为状态“存在”在组件内。但实际上,状态是由 React 保存的。React 通过组件在渲染树中的位置将它保存的每个状态与正确的组件关联起来。

在 React 中,屏幕中的每个组件都有完全独立的 state。举个例子,当你并排渲染两个 Counter 组件时,它们都会拥有各自独立的 score 和 hover state。

试试点击两个 counter 你会发现它们互不影响:

import React, {useState} from 'react';
import {Button, Card, Col, Row} from 'antd';

const App: React.FC = () => {
    return (
        <Row gutter={16}>
            <Counter/>
            <Counter/>
        </Row>
    );
}
export default App


const Counter = () => {
    const [score, setScore] = useState(0);
    return (
        <Col span={6}>
            <Card>
                <h1>{score}</h1>
                <Button type="primary" onClick={() => setScore(score + 1)}>
                    加一
                </Button>
            </Card>
        </Col>
    );
}

在这里插入图片描述

如你所见,当一个计数器被更新时,只有该组件的状态会被更新:
在这里插入图片描述
只有当在树中相同的位置渲染相同的组件时,React 才会一直保留着组件的 state。想要验证这一点,可以将两个计数器的值递增,取消勾选 “渲染第二个计数器” 复选框,然后再次勾选它:

import React, {useState} from 'react';
import {Button, Card, Col, Row} from 'antd';

const App: React.FC = () => {
    const [showB, setShowB] = useState(true);
    return (
        <>
            <Row gutter={16}>
                <Counter/>
                {showB && <Counter/>}
            </Row>
            <div style={{marginTop:30}}>
                <label>
                    <input
                        type="checkbox"
                        checked={showB}
                        onChange={e => {
                            setShowB(e.target.checked)
                        }}
                    />
                    渲染第二个计数器
                </label>
            </div>
        </>
    );
}
export default App


const Counter = () => {
    const [score, setScore] = useState(0);
    return (
        <Col span={6}>
            <Card>
                <h1>{score}</h1>
                <Button type="primary" onClick={() => setScore(score + 1)}>
                    加一
                </Button>
            </Card>
        </Col>
    );
}

![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c9c6deebe4fe41ec9301b8ea121a10ce.p
在这里插入图片描述
再次勾选
在这里插入图片描述

注意,当你停止渲染第二个计数器的那一刻,它的 state 完全消失了。这是因为 React 在移除一个组件时,也会销毁它的 state。
在这里插入图片描述
当你重新勾选“渲染第二个计数器”复选框时,另一个计数器及其 state 将从头开始初始化(score = 0)并被添加到 DOM 中。
在这里插入图片描述
只要一个组件还被渲染在 UI 树的相同位置,React 就会保留它的 state。 如果它被移除,或者一个不同的组件被渲染在相同的位置,那么 React 就会丢掉它的 state。

相同位置的相同组件会使得 state 被保留下来

在这个例子中,有两个不同的 标签:

import React, {useState} from 'react';
import {Button, Card, Col, Row} from 'antd';

const App: React.FC = () => {
    const [isFancy, setIsFancy] = useState(false);
    return (
        <>
            <Row gutter={16}>
                {isFancy ? (
                    <Counter isFancy={true} />
                ) : (
                    <Counter isFancy={false} />
                )}
            </Row>
            <div style={{marginTop:30}}>
                <label>
                    <input
                        type="checkbox"
                        checked={isFancy}
                        onChange={e => {
                            setIsFancy(e.target.checked)
                        }}
                    />
                    使用好看的样式
                </label>
            </div>
        </>
    );
}
export default App

interface CounterProp{
    isFancy:boolean
}
const Counter:React.FC<CounterProp> = ({ isFancy }) => {
    const [score, setScore] = useState(0);
    let className='counter';
    if (isFancy) {
        className += ' fancyClass';
    }
    return (
        <Col span={6}>
            <Card className={className}>
                <h1>{score}</h1>
                <Button type="primary" onClick={() => setScore(score + 1)}>
                    加一
                </Button>
            </Card>
        </Col>
    );
}

在这里插入图片描述
在这里插入图片描述
当你勾选或清空复选框的时候,计数器 state 并没有被重置。不管 isFancy 是 true 还是 false,根组件 App 返回的 div 的第一个子组件都是Counter:
在这里插入图片描述
它是位于相同位置的相同组件,所以对 React 来说,它是同一个计数器。

相同位置的不同组件会使 state 重置

在这个例子中,勾选复选框会将 Counter 替换为 p 标签:

import React, {useState} from 'react';
import {Button, Card, Col, Row} from 'antd';

const App: React.FC = () => {
    const [isFancy, setIsFancy] = useState(false);
    return (
        <>
            <Row gutter={16}>
                {isFancy ? (
                    <p>待会见!</p>
                ) : (
                    <Counter />
                )}
            </Row>
            <div style={{marginTop:30}}>
                <label>
                    <input
                        type="checkbox"
                        checked={isFancy}
                        onChange={e => {
                            setIsFancy(e.target.checked)
                        }}
                    />
                    休息一下
                </label>
            </div>
        </>
    );
}
export default App

const Counter= () => {
    const [score, setScore] = useState(0);
    return (
        <Col span={6}>
            <Card>
                <h1>{score}</h1>
                <Button type="primary" onClick={() => setScore(score + 1)}>
                    加一
                </Button>
            </Card>
        </Col>
    );
}

示例中,你在相同位置对 不同 的组件类型进行切换。刚开始

的第一个子组件是一个 Counter。但是当你切换成 p 时,React 将 Counter 从 UI 树中移除了并销毁了它的状态。

并且,当你在相同位置渲染不同的组件时,组件的整个子树都会被重置。要验证这一点,可以增加计数器的值然后勾选复选框:

import React, {useState} from 'react';
import {Button, Card, Col, Row} from 'antd';

const App: React.FC = () => {
    const [isFancy, setIsFancy] = useState(false);
    return (
        <>
            <Row gutter={16}>
                <Col span={6}>
                    {isFancy ? (
                        <div>
                            <Counter isFancy={true}/>
                        </div>
                    ) : (
                        <section>
                            <Counter isFancy={false}/>
                        </section>
                    )}
                </Col>
            </Row>
            <div style={{marginTop: 30}}>
                <label>
                    <input
                        type="checkbox"
                        checked={isFancy}
                        onChange={e => {
                            setIsFancy(e.target.checked)
                        }}
                    />
                    使用好看的样式
                </label>
            </div>
        </>
    );
}
export default App

interface CounterProp {
    isFancy: boolean
}

const Counter: React.FC<CounterProp> = ({isFancy}) => {
    const [score, setScore] = useState(0);
    let className = 'counter';
    if (isFancy) {
        className += ' fancyClass';
    }
    return (
        <Card className={className}>
            <h1>{score}</h1>
            <Button type="primary" onClick={() => setScore(score + 1)}>
                加一
            </Button>
        </Card>
    );
}

在这里插入图片描述
当你勾选复选框后计数器的 state 被重置了。虽然你渲染了一个 Counter,但是 div 的第一个子组件从 div 变成了 section。当子组件 div 从 DOM 中被移除的时候,它底下的整棵树(包含 Counter 以及它的 state)也都被销毁了。

一般来说,如果你想在重新渲染时保留 state,几次渲染中的树形结构就应该相互“匹配”。结构不同就会导致 state 的销毁,因为 React 会在将一个组件从树中移除时销毁它的 state。

在相同位置重置 state

默认情况下,React 会在一个组件保持在同一位置时保留它的 state。通常这就是你想要的,所以把它作为默认特性很合理。但有时候,你可能想要重置一个组件的 state。考虑一下这个应用,它可以让两个玩家在每个回合中记录他们的得分:

import React, {useState} from 'react';
import {Button, Card} from 'antd';

const App: React.FC = () => {
    const [isPlayerA, setIsPlayerA] = useState(true);
    return (
        <>
            {isPlayerA ? (
                <Counter person="Taylor" />
            ) : (
                <Counter person="Sarah" />
            )}
            <Button color="red" variant="solid" onClick={() => {
                setIsPlayerA(!isPlayerA);
            }}>
                下一位玩家!
            </Button>
        </>
    );
}
export default App

interface CounterProp {
    person: string
}

const Counter: React.FC<CounterProp> = ({person}) => {
    const [score, setScore] = useState(0);
    return (
        <Card style={{width:400}}>
            <h1>{person} 的得分: {score}</h1>
            <Button color="blue" variant="solid" onClick={() => setScore(score + 1)}>
                加一
            </Button>
        </Card>
    );
}

在这里插入图片描述

在这里插入图片描述
目前当你切换玩家时,分数会被保留下来。这两个 Counter 出现在相同的位置,所以 React 会认为它们是 同一个 Counter,只是传了不同的 person prop。

但是从概念上讲,这个应用中的两个计数器应该是各自独立的。虽然它们在 UI 中的位置相同,但是一个是 Taylor 的计数器,一个是 Sarah 的计数器。

有两个方法可以在它们相互切换时重置 state:

  1. 将组件渲染在不同的位置
  2. 使用 key 赋予每个组件一个明确的身份

方法一:将组件渲染在不同的位置

你如果想让两个 Counter 各自独立的话,可以将它们渲染在不同的位置:

return (
        <>
            {isPlayerA &&
                <Counter person="Taylor" />
            }
            {!isPlayerA &&
                <Counter person="Sarah" />
            }
            <Button color="red" variant="solid" onClick={() => {
                setIsPlayerA(!isPlayerA);
            }}>
                下一位玩家!
            </Button>
        </>
    );
  • 起初 isPlayerA 的值是 true。所以第一个位置包含了 Counter 的 state,而第二个位置是空的。
  • 当你点击“下一位玩家”按钮时,第一个位置会被清空,而第二个位置现在包含了一个 Counter。

在这里插入图片描述

每当 Counter 组件从 DOM 中移除时,它的 state 会被销毁。这就是每次点击按钮它们就会被重置的原因。

这个解决方案在你只有少数几个独立的组件渲染在相同的位置时会很方便。这个例子中只有 2 个组件,所以在 JSX 里将它们分开进行渲染并不麻烦。

方法二:使用 key 来重置 state

另一种更通用的重置组件 state 的方法。

你可能在 渲染列表 时见到过 key。但 key 不只可以用于列表!你可以使用 key 来让 React 区分任何组件。默认情况下,React 使用父组件内部的顺序(“第一个计数器”、“第二个计数器”)来区分组件。但是 key 可以让你告诉 React 这不仅仅是 第一个 或者 第二个 计数器,而且还是一个特定的计数器——例如,Taylor 的 计数器。这样无论它出现在树的任何位置, React 都会知道它是 Taylor 的 计数器!

在这个例子中,即使两个 会出现在 JSX 中的同一个位置,它们也不会共享 state:

return (
        <>
            {isPlayerA ? (
                <Counter key="Taylor" person="Taylor" />
            ) : (
                <Counter key="Sarah" person="Sarah" />
            )}
            <Button color="red" variant="solid" onClick={() => {
                setIsPlayerA(!isPlayerA);
            }}>
                下一位玩家!
            </Button>
        </>
    );

在 Taylor 和 Sarah 之间切换不会使 state 被保留下来。因为 你给他们赋了不同的 key。

指定一个 key 能够让 React 将 key 本身而非它们在父组件中的顺序作为位置的一部分。这就是为什么尽管你用 JSX 将组件渲染在相同位置,但在 React 看来它们是两个不同的计数器。因此它们永远都不会共享 state。每当一个计数器出现在屏幕上时,它的 state 会被创建出来。每当它被移除时,它的 state 就会被销毁。在它们之间切换会一次又一次地使它们的 state 重置。

请记住 key 不是全局唯一的。它们只能指定 父组件内部 的顺序。

摘要

  • 只要在相同位置渲染的是相同组件, React 就会保留状态。
  • state 不会被保存在 JSX 标签里。它与你在树中放置该 JSX 的位置相关联。
  • 你可以通过为一个子树指定一个不同的 key 来重置它的 state。
  • 不要嵌套组件的定义,否则你会意外地导致 state 被重置。

说明:文中例子参考React官方文档教程,不同的是这里使用TypeScript 来写,更详细的学习请移步React 官方文档教程

<think>好的,用户问的是React中如何规范地使用state普通变量。这个问题看起来是想了解在React组件中什么候应该用state,什么候可以用普通变量,以及两者的区别最佳实践。首先,我需要回忆React的基本概念,特别是状态管理组件的渲染机制。 用户可能已经知道React中的state是用来管理组件内部状态的,但可能对具体的使用场景区别不太清楚。他们可能在实际开发中遇到了状态更新不生效的问题,或者想知道为什么有候用普通变量也能工作,但有候不行。需要解释清楚两者的适用场景。 首先,要明确state的作用。Reactstate是响应式的,当state变化,组件会重新渲染,从而反映最新的数据。而普通变量在重新渲染会被重置,因为它们不在React的更新机制中。所以,如果数据需要影响UI,必须用state;如果只是临存储与渲染无关的值,可以用普通变量。 接下来,可能需要对比两者的使用场景。例如,表单输入的值需要绑定到state,因为用户输入会影响UI;而定器的ID可能用普通变量存储,因为不需要触发重新渲染。同,要提到useState钩子的正确用法,包括如何更新状态,避免直接修改state,而是使用setter函数。 另外,用户可能关心性能问题。比如,频繁更新state可能会导致不必要的渲染,这候可能需要使用useRef来存储可变值而不触发渲染,这也是普通变量的一种替代方案,但需要说明useRef普通变量的区别,比如在函数组件中,普通变量在每次渲染都会重置,而useRef的.current属性可以持久保存。 还需要提到常见的错误,比如在条件语句或循环中错误地使用useState,或者直接修改state对象导致组件不更新。此外,要强调在事件处理、异步操作中如何正确更新状态,避免闭包问题,比如在setTimeout中获取不到最新的state值,这候可能需要使用函数式更新或者useRef来保存最新值。 可能还需要举一些例子,比如计数器组件用state,而某个临计算值用普通变量。或者表单输入的例子,对比使用state普通变量的不同结果。这样用户能更直观地理解。 最后,总结使用原则:影响UI变化用state,反之用普通变量或ref。同,强调React的响应式机制,让用户明白为什么需要遵循这样的规范,比如保证组件的一致性、避免渲染问题等。</think>在React中,正确使用state普通变量是构建可靠组件的关键。以下是分步说明对比: 一、核心区别 1. state(状态): - 使用 `useState` 钩子创建 - 数据变化会触发组件重新渲染 - 用于存储需要持久化且影响UI的数据 - 示例:`const [count, setCount] = useState(0)` 2. 普通变量: - 通过 `let` 或 `const` 声明 - 变化不会触发重新渲染 - 用于临存储不影响UI的数据 - 示例:`let tempValue = ''` 二、使用场景对比 $$ \begin{array}{|l|l|l|} \hline \text{特征} & \text{State} & \text{普通变量} \\ \hline \text{持久性} & \text{跨渲染周期保留} & \text{每次渲染重置} \\ \text{触发渲染} & ✓ & × \\ \text{数据流向} & \text{单向数据流} & \text{临存储} \\ \text{适用场景} & \text{表单数据、UI状态} & \text{中间计算、临缓存} \\ \hline \end{array} $$ 三、正确使用state的规范 1. 不可直接修改: ```jsx // 错误 ❌ count = 5 // 正确 ✅ setCount(5) ``` 2. 函数式更新(当依赖前值): ```jsx setCount(prev => prev + 1) ``` 3. 状态提升原则: - 当多个组件需要共享状态,提升到最近的共同父组件 4. 复杂状态管理: - 使用 `useReducer` 代替多个关联的useState 四、普通变量适用场景 1. 中间计算结果: ```jsx function Component() { const [price] = useState(100) const tax = price * 0.1 // 普通变量 return <div>{tax}</div> } ``` 2. 事件处理中的临值: ```jsx function Form() { let inputValue = '' // 临存储 const handleChange = (e) => { inputValue = e.target.value } // 注意:这种写法实际无法正常工作,需配合state使用 } ``` 五、特殊案例:使用useRef 当需要满足: - 值变化不触发渲染 - 跨渲染周期保持引用 ```jsx const intervalId = useRef(null) // 设置定器 intervalId.current = setInterval(...) // 清除定器 clearInterval(intervalId.current) ``` 六、最佳实践原则 1. 遵循最小state原则: - 不要创建不需要的state - 能通过props计算的值不要存入state 2. 状态拆分策略: ```jsx // 错误 ❌ const [user, setUser] = useState({ name: '', age: 0 }) // 正确 ✅(当字段独立变化) const [name, setName] = useState('') const [age, setAge] = useState(0) ``` 3. 性能优化: - 避免在渲染函数中进行高开销计算 - 使用 `useMemo` 缓存计算结果 示例:规范的表单处理 ```jsx function LoginForm() { const [username, setUsername] = useState('') const [password, setPassword] = useState('') let submitAttempts = 0 // 普通变量记录提交次数(不触发渲染) const handleSubmit = () => { submitAttempts++ if (username && password) { // 提交逻辑 } } return ( <> <input value={username} onChange={e => setUsername(e.target.value)} /> <input type="password" value={password} onChange={e => setPassword(e.target.value)} /> <button onClick={handleSubmit}>登录</button> </> ) } ``` 总结选择依据: $$ \text{是否需要触发UI更新} \Rightarrow \begin{cases} \text{需要} & \Rightarrow \text{使用State} \\ \text{不需要} & \Rightarrow \begin{cases} \text{跨渲染周期} & \Rightarrow \text{useRef} \\ \text{临存储} & \Rightarrow \text{普通变量} \end{cases} \end{cases} $$
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值