为什么将组件存储为状态值是不好的做法?

const [Component, setComponent] = useState<JSX.Element>(<Empty />);

假设我想根据许多不同的标准(所有标准都是互斥的)有条件地呈现一个组件.但在实际渲染之前,我想添加一个取消保镖(在x毫秒不活动后延迟渲染).我不一定会这样做,但它似乎更直观,而且只将组件指定为状态值(在此场景中)代码更少.我可以将状态设置为保存一个文本值,在任何地方引用该文本值,并设置一个映射变量将该字符串映射到一个组件.但这似乎没有必要.I've read online that it's bad practice,你应该只把数据放入状态,但似乎每个人都很方便省略了why,这是一个糟糕的做法.Nothing in the docs似乎也表明这是一种糟糕的做法.

这里有一个有效的例子,希望能说明为什么将组件设置在状态很方便.Message个组件中的每个组件都有React.memo个标记,因此它们的props 不可能发生变化:

import React, { useState, useEffect } from 'react';
import useDebounce from '../../hooks/useDebounce';
import {
  TooShort,
  NoPrompt,
  LimitWarning,
  LimitReached,
  Empty,
} from './Messages';

interface Props {
  promptAreaTouched: boolean;
  promptText: string;
  debounceTimeout?: number;
}

const DEBOUNCE_TIMEOUT = 2000;
const SHORT_PROMPT_LENGTH = 5;
const APPROACHING_PROMPT_LENGTH = 40;
const MAX_PROMPT_LENGTH = 50;

const PromptLengthMessage = ({
  promptAreaTouched,
  promptText,
  debounceTimeout = DEBOUNCE_TIMEOUT,
}: Props) => {
  const [Component, setComponent] = useState<JSX.Element>(<Empty />);
  const DebouncedComponent = useDebounce(Component, debounceTimeout);

  const numWords = promptText.split(/\s+/).length;

  const isEmpty = promptAreaTouched && promptText.length === 0;
  const isTooShort = promptAreaTouched && numWords <= SHORT_PROMPT_LENGTH;
  const limitWarning =
    numWords >= APPROACHING_PROMPT_LENGTH && numWords < MAX_PROMPT_LENGTH;
  const limitReached = numWords >= MAX_PROMPT_LENGTH;

  useEffect(() => {
    switch (true) {
      case isEmpty:
        setComponent(<NoPrompt />);
        break;
      case isTooShort:
        setComponent(<TooShort />);
        break;
      case limitWarning:
        setComponent(<LimitWarning />);
        break;
      case limitReached:
        setComponent(<LimitReached />);
        break;
      default:
        setComponent(<Empty />);
    }
  }, [promptText]);

  return DebouncedComponent === Component ? DebouncedComponent : <Empty />;
};

export default PromptLengthMessage;

推荐答案

状态是你所说的词,而不是你说话时的嘴型.

国家是一本书,而不是它在书架上的样子.

状态是消息的问题所在,而不是警告是如何呈现的.

您呈现的是not状态.相反,您渲染的是按州显示的informed.

州是meaning.当这一含义改变时,reacts通过declaratively呈现不同的内容来对这种改变做出react .


将渲染内容设置为状态是imperative代码.这是你说当我点击这个按钮时,我想呈现这个内容.

乍听起来可能很好,但这就像是这样做的:

button.onClick = () => document.getElementById('message').innerHtml = 'Too Short!'

但更好的是:

当我点击这个按钮时,我想要更改我的用户界面的状态,然后是the UI will render itself correctly.,这是declarative代码.它描述了UI将如何在各种状态下呈现.

这可能是这样的:

<button onClick={() => setMessage('tooShort')} />
{
  message === 'tooShort' && (
    <p>your message <code>{enteredText}</code> is too short</p>
  )
}

现在设置新状态很简单,呈现的内容是从该状态派生的.

在Reaction中,声明性代码是好的,而命令性代码是要避免的.


所以,当你这样做的时候,你不能为任何事情都达到这种状态,除了在某个地方吐出来.

如果您将JSX设置为状态,下面是一些更难完成的事情.


您不能使用该状态通知多个渲染位置.假设您收到了消息,但您也有许多这样的字段,并且您希望计算错误数,并且希望从该计数中排除Empty.如果是JSX就不行了.


更难测试.假设您想让它成为一个带有测试的定制钩子和盖子.一个你做不到的好测试可能是:

expect(useMessageError('a').error).toBe('tooShort')

挂钩依赖项.

useEffect(() => {}, [myComponentState]) // may re-render since <></> !== <></>
useEffect(() => {}, [myStringState]) // stable because 'empty' === 'empty'

我想您会发现,如果您try 让任何涉及到此代码的内容变得更复杂一点,这种方法就会开始崩溃.当大量使用时,这些代码会变得一团糟.

我的建议是embracereact 的陈述性范式.它以这种方式设置是有充分理由的.让您的状态保留数据和意义,并让您的渲染做它最擅长的事情,即基于该状态进行渲染.

Typescript相关问答推荐

动态判断TypScript对象是否具有不带自定义类型保护的键

Angular 17 Select 错误core.mjs:6531错误错误:NG 05105:发现意外合成监听器@transformPanel.done

使用Angular和API请求在CRUD操作后更新客户端数据的良好实践

React router 6.22.3不只是在生产模式下显示,为什么?

如何判断输入是否是TypeScript中的品牌类型?

类型脚本强制泛型类型安全

在Angular中注入多个BrowserModules,但在我的代码中只有一个

在类型脚本中的泛型类上扩展的可选参数

AngularJS服务不会解析传入的Json响应到管道中的模型

当数组类型被扩展并提供标准数组时,TypeScrip不会抱怨

Typescript,如何在通过属性存在过滤后重新分配类型?

KeyOf关键字在特定示例中是如何工作的

扩展对象的此内容(&Q;)

在类型脚本中创建显式不安全的私有类成员访问函数

Typescript泛型调用对象';s方法

TypeScript中的这些条件类型映射方法之间有什么区别?

Typescript泛型验证指定了keyof的所有键

React HashRouter,单击导航栏时页面重新加载出现问题

Typescript 编译错误:TS2339:类型string上不存在属性props

如何在故事书的Typescript 中编写decorator ?