【React】798- 使用 React&Mobx 的几个最佳实践_sed

Mobx 是我非常喜欢的 React 状态管理库,它非常灵活,同时它的灵活也会给开发带来非常多的问题,因此我们在开发的时候也要遵循一些写法上的最佳实践,使我们的程序达到最好的效果。

在 store 中维护业务逻辑

尽量不要把业务逻辑写在 ​​React Component​​ 里面。当你把业务逻辑写在组件里面的时候,很难及时定位错误的,因为业务逻辑分散在各种不同的组件里面,让你很难来通过行为来定义到底是哪些代码涉及的这个错误,不同组件复用这些逻辑也很困难。

最好在 ​​stores​​​ 中把业务逻辑编写成方法,并在你的 ​​Component​​ 中调用这些方法。

只允许在 store 中修改属性

尽量不要在一个 ​​Component​​​ 里直接修改一个 ​​store​​​ 的属性。只有 ​​store​​ 本身可以修改他自己的属性。

当你要改变属性的时候,请调用相应的 ​​store​​ 方法。不然的话你的属性修改会散落在各处不受控制,这是很难调试的。

class Store {
@observable text;

@action.bound
handleSearch = value {
this.text = value
}
}

const store = new Store();

@observer
class Home extends Component {

handleChanged = (event) => {
store.handleSearch(event.target.value);
}

render() {
return (
<input
value={store.searchText}
onChange={this.handleChanged}
/>
);
}
}

所有属性更改都用 action

使用 ​​action​​ 后,可以清楚的看出哪些代码可以更改可观察的变量,并且方便调试工具给出更多的信息

使用 ​​transaction​​​ 可以将多个应用状态(​​Observable​​​)的更新视为一次操作,并只触发一次监听者(​​Reactions​​​)的动作(UI更新、网络请求等),避免多次重复渲染。​​action​​​中封装了​​transaction​​​,多次改变​​@observable​​变量时,只会重新渲染一次,提高了性能。

class Store {
@observable name;
@observable age;

@action
change(name,age){
this.name = name;
this.age = age;
}
}

从 store 中分离出 API 请求

不要在你的 ​​store​​​ 里调用 ​​API​​​ 接口,这会让它们很难测试,也让代码变的更复杂和耦合。额外建一个类,把 ​​API​​​ 接口调用放进去,并在 ​​store​​​ 的构造函数里实例化他们来使用。当你编写测试代码时,你可以很容易地模拟这些 ​​api​​​ 并把你的模拟 ​​api​​​ 实例传给每一个 ​​store​​。

class UserService {

fetchUser = () axios.get('/api/user')
}

class Store {

@observable todos = [];

constructor(userService) {
this.userService = userService;
}

fetchUser = async () => {
const todos = await this.userService.fetchUser();

runInAction(() {
this.todos = todos;
});
}
}

const userService = new UserService();
const store = new Store(userService);

对每一个 component 都声明 @observer

​@observer​​​ 可以用来将 ​​React​​​ 组件转变成响应式组件。它用 ​​mobx.autorun​​​ 包装了组件的 ​​render​​ 函数以确保任何组件渲染中使用的数据变化时都可以强制刷新组件。

给每一个 ​​component​​​ 都标注 ​​@observer​​​ ,这可以使得他们可以随着 ​​store prop​​​ 的改变而更新。如果子组件没有标注 ​​@observer​​​ 的话,就会导致其父 ​​component​​​ (有 ​​@observer​​​ )刷新。因此我们要尽可能的让每个子 ​​component​​​ 都标注 ​​@observer​​ ,这可以减少不必要的重新渲染。

不要缓存 observables 属性

​Observer​​​ 组件只会追踪在 ​​render​​​ 方法中存取的数据。如果你从 ​​observable​​ 属性中提取数据并将其缓存在组件里,这样的数据是不会被追踪的:

class Store {
@observable name;
@observable age;
}

class Home extends React.Component {

componentWillMount() {
// 错误的,info 的更新不会被追踪
this.info = store.name + store.age
}

render() {
return <div>{this.info}</div>
}
}

使用 @computed

比如刚刚的例子,使用 ​​@computed​​​ 属性来处理一些涉及多个属性的逻辑。使用 ​​@computed​​ 可以减少这样的判断类业务逻辑在组件里面出现的频率。

class Store {
@observable name;
@observable age;

@computed info = () {
return this.name + this.age;
}
}

class Home extends React.Component {

render() {
return <div>{store.info}</div>
}
}

多编写受控组件

多编写可控组件,这样会大大降低你的测试复杂度,也让你的组件易于管理。

当需要追踪对象属性时、使用 map

​MobX​​​ 可以做许多事,但是它无法将原始类型值转变成 ​​observable​​​ (尽管可以用对象来包装它们)。所以说值不是 ​​observable​​​,而对象的属性才是。这意味着 ​​@observer​​​ 实际上是对间接引用值作出反应。所以如果像下面这样初始化的话,​​Timer​​ 组件是不会作出任何反应的:

ReactDom.render(<Timer timerData={timerData.secondsPassed} />, document.body)

在这行代码中,只是 ​​secondsPassed​​​ 的当前值传递给了 ​​Timer​​​,这个值是不可变值 (JS中的所有原始类型值都是不可变的)。这个值永远都不会改变,所以 ​​Timer​​​ 也永远不会更新。 ​​secondsPassed​​​ 属性将来会改变,所以我们需要在组件内访问它。或者换句话说: 永远只传递拥有 ​​observable​​ 属性的对象。

如果你想追踪对象中每个属性的变更,可以使用 ​​map​​:

​observable.map(values?)​​​ 创建一个动态键的 ​​observable​​​ 映射。如果你不但想对一个特定项的更改做出反应,而且对添加或删除该项也做出反应的话,那么 ​​observable​​ 映射会非常有用。

class Store {
timerData = @observable.map({secondsPassed:0});

@action.bound
change(value){
this.timerData.set('secondsPassed',value);
}
}

class Home extends React.Component {

render() {
return <div>{store.timerData.get('secondsPassed')}</div>
}
}



【React】798- 使用 React&Mobx 的几个最佳实践_业务逻辑_02