前言
从类组件到函数组件,从state、props到store、context,我跟状态打的交道越多,遇到的疑惑也就越多。偶然间重读React官方文档,看到他们对状态的哲学阐述,深受启发,所以在此给出我的一些理解。
React状态哲学
首先来看一些官方对于状态的定义
React通过state让ui具备交互的能力
做好状态管理,分两步走:
- 确定 UI state 的最小(且完整)表示
- 该数据是否是由父组件通过 props 传递而来的?如果是,那它应该不是 state。
- 该数据是否随时间的推移而保持不变?如果是,那它应该也不是 state。
- 你能否根据其他 state 或 props 计算出该数据的值?如果是,那它也不是 state。
- 确定 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>;
};
后记
欢迎指导,欢迎捉虫~