随着全局状态管理库如vuex,mobx,redux的盛行,人们越来越习惯把状态一股脑丢在状态管理库里面,却忘了去划分状态的级别。这种滥用全局状态管理库的现象一直普遍存在。
什么叫滥用全局状态管理库?
就是没有认识到状态管理库的根本作用,或者说我们什么时候才需要状态管理库?
拿 react 来说,react 是有组内状态的,状态可以通过 props 传递。但是,但当 app 比较庞大的时候,兄弟组件,远亲组件这些的交流就变得困难起来, 它们必须依赖相同的父组件来完成信息的传递。这时,就是我们使用状态管理库的时候。
但是,很多人把所有状态都往 redux 里面丢,虽然这方便了开发,但缺点却很明显:
- 组件很难复用:因为状态保存在global,所以只有一份。你在多个地方使用了这个组件,但因为状态只有一份,这个组件的多个实例是相互影响的,这很糟糕。
- 耦合度高:根据高内聚低耦合的设计原则,我们在设计组件的时候应该保证这个组件有独立的功能,通过props来跟外界交流。但如果把组件的状态放在全局 model而不是放在组件内部,就违背了内聚这一规则。其次,也提供了让其他地方修改这份model的能力,使用这个组件的人不得不担心是否有地方影响到这份model,这很糟糕。
正确的做法
划分好状态的等级,尽量把状态放在组件内。当遇到需要共享组内状态的时候,提升状态到父级或者全局状态管理库。
你可以把view、model、loadData逻辑都塞进这个组件,这样它便有了独立功能。它把复杂度隐藏在内部,然后向外界暴露一些api来提供自己的能力。
例如一个列表组件:
// 方案一
// ListDemo.jsx
import React,{useEffect} from 'react';
import {getData} from 'services/api';
export default function ListDemo({requestId}){
// model
const [data,setData] = useState([]);
const [visible,setVisible] = useState(false);
useEffect(()=>{
// services 层
getData().then(data=>{
setData(data)
});
/**
* 当requestId变化时,列表会重新请求
* 这里的requestId是组件向外界暴露的一个api
**/
},[requestId])
useEffect(()=>{
if(visible===true){
// clearState
setVisible(false);
}
},[requestId])
return (
// view
<div>
{
data.map(item=><li>{item}</li>
}
{
visible && (
<div>
this is a modal
</div>
)
}
</div>
)
)
}
// app.jsx
<ListDemo />
这种组件设计的特点是,组件可以重置自身状态的时机是由自身控制的。如果你觉得这样麻烦,你可以把重置自身状态的时机交给外部,通过key来 “销毁组件”=>“重新渲染组件”。上面的代码可以简化成:
// 方案二
// ListDemo.jsx
import React,{useEffect} from 'react';
import {getData} from 'services/api';
export default function ListDemo({requestId}){
// model
const [data,setData] = useState([]);
const [visible,setVisible] = useState(false);
useEffect(()=>{
// services 层
getData().then(data=>{
setData(data)
});
},[])
return (
// view
<div>
{
data.map(item=><li>{item}</li>
}
{
visible && (
<div>
this is a modal
</div>
)
}
</div>
)
)
}
// app.jsx
/*
*当requestId变化时,ListDemo会重新渲染
*/
<ListDemo key={requestId} />
方案二的代码比较整洁,且出错率比方案一低,但是方案二存在重新渲染组件的一个环节,性能开支会比方案一多一点点(大部分情况你都可以忽略不计)。很多情况下,我都会采用方案二。