22

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

 3 years ago
source link: https://mp.weixin.qq.com/s?__biz=Mzg2NDAzMjE5NQ%3D%3D&%3Bmid=2247487136&%3Bidx=1&%3Bsn=7d7d76263ff91aa0a5e26e2438f8ee7b
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

6zIF7r3.jpg!mobile

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

YZzamaU.jpg!mobile


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK