使用 React&Mobx 的几个最佳实践

共 3410字,需浏览 7分钟

 ·

2020-12-01 11:07

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>
  }
}




推荐阅读




我的公众号能带来什么价值?(文末有送书规则,一定要看)

每个前端工程师都应该了解的图片知识(长文建议收藏)

为什么现在面试总是面试造火箭?

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报