React状态哲学 & 状态管理的实践总结

891 阅读4分钟

前言

从类组件到函数组件,从statepropsstorecontext,我跟状态打的交道越多,遇到的疑惑也就越多。偶然间重读React官方文档,看到他们对状态的哲学阐述,深受启发,所以在此给出我的一些理解。

React状态哲学

首先来看一些官方对于状态的定义

React通过stateui具备交互的能力

做好状态管理,分两步走:

  1. 确定 UI state 的最小(且完整)表示
  • 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
  • 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
  • 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
  1. 确定 state 放置的位置
  • 找到根据这个 state 进行渲染的所有组件。
  • 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
  • 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
  • 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。

我的理解

反面例子🌰

场景:创建一个H5页面,该页面有三个Tab页,现在需要做到点击每个Tab都会刷新该Tab页组件。

第一版

实现思路:分为一个父组件和两个子组件,子组件是否需要刷新由父组件的state控制。

先创建两个state,在点击TabPane的时候,将对应的freshTab这个状态置为true

import React, { useState, useEffect } from "react";
import { Tabs } from "antd-mobile";

const { TabPane } = Tabs;

export const Father = () => {
  const [freshTab1, setFreshTab1] = useState(false);
  const [freshTab2, setFreshTab2] = useState(false);

  return (
    <Tabs>
      <TabPane onPress={() => setFreshTab1(true)}>
        <Tab1 needFresh={freshTab1} setFreshToFalse={setFreshTab1(false)} />
      </TabPane>
      <TabPane onPress={() => setFreshTab2(true)}>
        <Tab2 needFresh={freshTab2} setFreshToFalse={setFreshTab2(false)} />
      </TabPane>
    </Tabs>
  );
};

--------
const Tab1 = (props: { needFresh, setFreshToFalse }) => {
  const [fresh, setFresh] = useState(props.needFresh);

  //如果fresh为true,刷新当前页面,并将刷现状态置为false
  useEffect(() => {
    if (fresh) {
      freshCurTab();
      setFreshToFalse();
    }
  }, [fresh]);

  //刷新当前页面
  const freshCurTab = () => {};

  return <p>我是Tab1</p>;
};


--------
const Tab2 = (props: { needFresh, setFreshToFalse }) => {
  const [fresh, setFresh] = useState(props.needFresh);

  useEffect(() => {
    if (fresh) {
      freshCurTab();
      setFreshToFalse();
    }
  }, [fresh]);

  const freshCurTab = () => {};

  return <p>我是Tab2</p>;
};

第二版(根据React状态管理的第一个原则进行优化)

1.1 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state

在该实例中,控制子组件是否刷新的状态是由props传进来的,可以直接使用,不需要用额外的state去保存

1.2该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。

我对这一点理解是,用来提醒我们检查状态里是否有常量,如果有,声明一个变量保存即可,不需要用户state保存

1.3你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。

部分变量是由state和props综合计算得出的,并且该计算比较复杂,我们不可能在每个需要用到它的地方把计算过程重写一遍,所以会习惯性的用一个state去保存计算的过程,但这依旧是不可取的,而且useMemo这个hook可以解决这种难题

根据第一规则优化过的代码如下:

import React, { useState, useEffect } from "react";
import { Tabs } from "antd-mobile";

const { TabPane } = Tabs;

export const Father = () => {
  const [freshTab1, setFreshTab1] = useState(false);
  const [freshTab2, setFreshTab2] = useState(false);

  return (
    <Tabs>
      <TabPane onPress={() => setFreshTab1(true)}>
        <Tab1 needFresh={freshTab1} setFreshToFalse={setFreshTab1(false)} />
      </TabPane>
      <TabPane onPress={() => setFreshTab2(true)}>
        <Tab2 needFresh={freshTab2} setFreshToFalse={setFreshTab2(false)} />
      </TabPane>
    </Tabs>
  );
};

--------
const Tab1 = (props: { needFresh, setFreshToFalse }) => {
  const {needFresh , setFreshToFalse} = props;

  //如果fresh为true,刷新当前页面,并将刷现状态置为false
  useEffect(() => {
    if (needFresh) {
      freshCurTab();
      setFreshToFalse();
    }
  }, [needFresh]);

  //刷新当前页面
  const freshCurTab = () => {};

  return <p>我是Tab1</p>;
};


--------
const Tab2 = (props: { needFresh, setFreshToFalse }) => {
  const {needFresh , setFreshToFalse} = props;

  useEffect(() => {
    if (needFresh) {
      freshCurTab();
      setFreshToFalse();
    }
  }, [needFresh]);

  const freshCurTab = () => {};

  return <p>我是Tab2</p>;
};

第三版(根据React状态管理的第二个原则进行优化)

  • 找到根据这个 state 进行渲染的所有组件。
  • 找到他们的共同所有者(common owner)组件(在组件层级上高于所有需要该 state 的组件)。
  • 该共同所有者组件或者比它层级更高的组件应该拥有该 state。
  • 如果你找不到一个合适的位置来存放该 state,就可以直接创建一个新的组件来存放该 state,并将这一新组件置于高于共同所有者组件层级的位置。

我对这一点的理解就是,将相关联的各个组件中的状态放在一起看,相关联的或者有相同作用的状态,提升到他们的父组件中管理,在此例中,多个Tab页的刷新状态可不可以用同一个状态进行管理

根据第二规则优化过的代码如下:

import React, { useState, useEffect } from "react";
import { Tabs } from "antd-mobile";

const { TabPane } = Tabs;

export const Father = () => {
  const [freshTab, setFreshTab] = useState(false);
  const [curTab, setCurTab] = useState("");

  return (
    <Tabs>
      <TabPane
        onPress={() => {
          setFreshTab(true);
          setCurTab("Tab1");
        }}
      >
        <Tab1
          needFresh={freshTab}
          curTab={curTab}
          setFreshToFalse={setFreshTab(false)}
        />
      </TabPane>
      <TabPane
        onPress={() => {
          setFreshTab(true);
          setCurTab("Tab2");
        }}
      >
        <Tab2
          needFresh={freshTab}
          curTab={curTab}
          setFreshToFalse={setFreshTab(false)}
        />
      </TabPane>
    </Tabs>
  );
};

const Tab1 = (props: { needFresh; curTab; setFreshToFalse }) => {
  const { needFresh, curTab, setFreshToFalse } = props;

  //如果fresh为true,刷新当前页面,并将刷现状态置为false
  useEffect(() => {
    if (needFresh && curTab === "Tab1") {
      freshCurTab();
      setFreshToFalse();
    }
  }, [needFresh]);

  //刷新当前页面
  const freshCurTab = () => {};

  return <p>我是Tab1</p>;
};

const Tab2 = (props: { needFresh; curTab; setFreshToFalse }) => {
  const { needFresh, curTab, setFreshToFalse } = props;

  useEffect(() => {
    if (needFresh && curTab === "Tab2") {
      freshCurTab();
      setFreshToFalse();
    }
  }, [needFresh]);

  const freshCurTab = () => {};

  return <p>我是Tab2</p>;
};

后记

欢迎指导,欢迎捉虫~