mobx使用数组提示越界_初识Mobx

MobX是一个简单、可扩展、久经沙场的状态管理解决方案。这篇文章主要是翻译自mobx官网上的十分钟mobx教程来介绍Mobx以及自己在学习Mobx中的一些浅显总结。官网教程
https://mobx.js.org/getting-started.html

为什么使用Mobx

在使用Reactjs过程中,经常会遇到一些state相关的问题和bug。主要有以下几个问题:
setState异步导致state更新不及时引起的bug
如果修改一些state,然后马上去调用某个state,得到的是之前的值,并不是修改后的值。这是因为setState是异步的原因,这种bug是经常易犯的错误。比如下面的代码,显示当前选中元素的值,实际效果显示的是上一个选中元素的值。
import React from 'react';import logo from './logo.svg';import './App.css';class App extends React.Component {constructor(props, context) {super(props, context)this.state = {
      selection: props.values[0]
    };
  }
  render() {return (
this.onKeyDown} tabIndex={0}>
        {this.props.values.map(value =>            className={value === this.state.selection ? 'selected' : ''}
            key={value}
            onClick={() => this.onSelect(value)}
          >
            {value}
        )}  
    )
  }  
  onSelect(value) {this.setState({
      selection: value
    };//此处,setState后在fireOnSelect中使用state的值,因此不是用户想要的结果this.fireOnSelect();
  }
  onKeyDown = (e) => {
    const {values} = this.props
    const idx = values.indexOf(this.state.selection)if (e.keyCode === 38 && idx > 0) { /* up */this.setState({
        selection: values[idx - 1]
      })
    } else if (e.keyCode === 40 && idx -1) { /* down */this.setState({
        selection: values[idx + 1]
      })
    }this.fireOnSelect();
  }
  fireOnSelect() {if (typeof this.props.onSelect === "function")this.props.onSelect(this.state.selection) /* not what you expected..*/
  }
}
export default App; 
ReactDOM.render("State.", "Should.", "Be.", "Synchronous."]} 
    onSelect={value => console.log(value)}
/>,
  document.getElementById('root')
);
针对上面的问题,解决方法比较简单,可以使用一个变量来存储该值,或者使用setState的callback来实现。下面是修改后的代码:
onSelect(value) {this.setState({
      selection: value
    },()=>{this.fireOnSelect()
    })
  }
setState会引起不必要的渲染(render)state值的变化会使页面重新渲染,但很多状态的变化引起渲染是不必要的。粗略地说,认为一次重新渲染是必要的有以下原因:
  • state新设置的值和上一次的值完全一样。这种情况通常可以通过实现shouldComponentUpdate生命周期来解决,或者你已经在使用一些库(pure render)来解决这个问题。
  • 有时state的修改与UI显示无关。
  • 并不是所有组件状态都应该使用setState存储和更新。很多复杂组件通常需要使用生命周期函数来管理一些内容,例如:计时器、网络请求、事件等。使用setState管理这些复杂组件的状态不仅会触发重新渲染,还会导致一些相关的生命周期函数再次被触发,进而导致一些奇怪的状况。
使用MobX管理局部组件状态可以避免上面因为state的变化而导致的上述问题,Mobx帮你进行了状态的管理。

MobX的核心概念

Mobx和Redux一样,采用单向数据流管理状态:通过action改变应用的state,state的改变触发相应UI的更新。 b995987abd38603768a3e270f9f459ce.png状态管理是每个应用的核心,不一致的状态数据或者与本地变量不同步的情况会导致一些奇怪的bug,比如上面的例子。因此许多状态管理方案都尝试限制修改状态数据的方式,比如让状态数据不可变。MobX从根本上让状态管理变得简单:它不会再次建出一个不一致状态。方法也很简单:确保每个源自于应用状态的内容能被自动感知到。 State:存在一个应用级别的状态。对象图表、数组、原始值、引用组成了应用的模型数据。这些值是应用的数据单元。是应用依赖的最小状态集,没有任何多余的状态。 Derivation:也是Computed value任何值都能从应用状态里自动计算获得。这些派生,或者说是计算属性,可以是简单值,比如未完成事项的数量,也可以是像表现未完成事项的HTML展示这种复杂的内容。在电子表格里,这些就是应用的公式和图表。 计算 值 ,是根据state推导计算出来的值 Reaction:响应(reaction)跟派生很像。主要区别在于这些方法不会生产出值。相反,它们会自动运行一些任务。通常这些都是I/O相关的。它们保证DOM能被更新或者网络请求在合适的时间发出来。 Action:动作用来改变状态。MobX保证改变应用状态的动作能被所有派生和响应自动处理,同步并且不会出问题。 建议 是唯一可以修改状态的方式
这些翻译过来的文字读起来比较绕口,但结合下面的例子就能理解每个概念的意思.

一步步演示MobX的使用

简单的todo应用
下面是官网上的具体实例,而且官网上提供了run code,可以看到每个Demo的演示效果。
class TodoStore {
    todos = [];
    get completedTodosCount() {return this.todos.filter(todo => todo.completed === true
        ).length;
    }
    report() {if (this.todos.length === 0)return "";const nextTodo = this.todos.find(todo => todo.completed === false);return `Next todo: "${nextTodo ? nextTodo.task : ""}". ` +`Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }
    addTodo(task) {this.todos.push({task: task,completed: false,assignee: null
        });
    }
}const todoStore = new TodoStore();
上面的代码是一个基本的数据模型,里面包含数据存储,以及改变数据存储的方法。在没有使用MobX之前,往数据模型中增加项目时,需要手动的调用report来跟踪变化。如下代码:
todoStore.addTodo("read MobX tutorial");console.log(todoStore.report());
todoStore.addTodo("try MobX");console.log(todoStore.report());
todoStore.todos[0].completed = true;console.log(todoStore.report());
todoStore.todos[1].task = "try MobX in own project";console.log(todoStore.report());
todoStore.todos[0].task = "grok MobX tutorial";console.log(todoStore.report());
变成响应式应用
上面的代码状态数据的变化需要程序主动的调用report去刷新,因此不是响应式应用,如何使其变成响应式应用来简化开发工作,这就是MobX的作用。根据状态数据可以自动地执行代码。然后我们的report方法里的打印自动更新就像电子表格一样。为实现这个目标,TodoStore需要变成可观察的,使得MobX可以追踪变化。我们让一些属性通过@observable装饰器变得可观察的了,并能主动告知MobX这些变化。计算属性通过@computed装饰器来自动从状态数据中派生。
class ObservableTodoStore {@observable todos = [];@observable pendingRequests = 0;constructor() {
        mobx.autorun(() => console.log(this.report));
    }@computed get completedTodosCount() {return this.todos.filter(
            todo => todo.completed === true
        ).length;
    }@computed get report() {if (this.todos.length === 0)return "";
        const nextTodo = this.todos.find(todo => todo.completed === false);return `Next todo: "${nextTodo ? nextTodo.task : ""}". ` +
            `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }
    addTodo(task) {this.todos.push({
            task: task,
            completed: false,
            assignee: null
        });
    }
}
const observableTodoStore = new ObservableTodoStore();
在构造方法里我们创建了一个小方法用来打印report并用autorun包裹它。Autorun创建一个只运行一次的响应(reaction),它能在每次可观察数据变化后自动运行。因为report方法使用到了可观察的todos属性,它将会在合适的时机打印数据。下面的代码显示了这个特性。mobx生成依赖两个装饰器,@computed和@observable配合使用。简单点说,observable让变量可跟踪,computed,当observable变量变化就会调用一下去计算是否需要更新ui。
observableTodoStore.addTodo("read MobX tutorial");
observableTodoStore.addTodo("try MobX");
observableTodoStore.todos[0].completed = true;
observableTodoStore.todos[1].task = "try MobX in own project";
observableTodoStore.todos[0].task = "grok MobX tutorial";
日志被自动打印,同时也没有遗漏中间过程的数据,不需要再主动的调用report方法。但注意observableTodoStore.todos[0].task = "grok MobX tutorial";没有任何的输出,尽管状态数据todos发生了变化,但是report方法却没有被触发,因为todos[0].task变化没有导致report的变化。
Next todo: "read MobX tutorial". Progress: 0/1Next todo: "read MobX tutorial". Progress: 0/2Next todo: "try MobX". Progress: 1/2Next todo: "try MobX in own project". Progress: 1/2
但如果改成下面的代码,report就会被调用。 这个例子很好体现了autorun不仅监测了todos数组对象,还检测了是否需要更新。
@computed get report() {if (this.todos.length === 0)return "";return `Next todo: "${this.todos[0].task}". ` +
             `Progress: ${this.completedTodosCount}/${this.todos.length}`;
    }
使React也变成响应式
React组件没有做到开箱即用,mobx提供了一个装饰器,包裹了组件,让render方法自动调用,保证你的组件和状态的同步。使用了mobx之后,就不需要再调用setState了,组件会变得 “智能”。React的组件并不是开箱即为响应式的。mobx-react的@observer修饰器能让React组件的render方法放入autorun中,自动在组件和状态数据之间同步,同上面的report是异曲同工的效果。
跟MobX有关的其实只有@observer装饰器。但保证每个组件各自在相关数据变化时重新渲染已是足够了。
@observerclass TodoList extends React.Component {
   render() {
     const store = this.props.store;return (

         { store.report }

         { store.todos.map((todo, idx) => 
         ) }
         { store.pendingRequests > 0 ? Loading... : null }this.onNewTodo }>New Todo (double-click a todo to edit)

     );
   }   onNewTodo = () => {this.props.store.addTodo(prompt('Enter a new todo:','coffee plz'));
   }
 }
 @observerclass TodoView extends React.Component {
   render() {
     const todo = this.props.todo;return (this.onRename }>           type='checkbox'
           checked={ todo.completed }
           onChange={ this.onToggleCompleted }
         />
         { todo.task }
         { todo.assignee
           ? { todo.assignee.name }
           : null
         }
     );
   }   onToggleCompleted = () => {
     const todo = this.props.todo;
     todo.completed = !todo.completed;
   }   onRename = () => {
     const todo = this.props.todo;
     todo.task = prompt('Task name', todo.task) || todo.task;
   }
 }
 ReactDOM.render(,document.getElementById('reactjs-app')
 );
下面的代码很好的显示了仅仅只要改变数据而不用做任何别的事情。MobX会自动计算衍生数据,用状态数据更新相关的用户界面。
const store = observableTodoStore;
 store.todos[0].completed = !store.todos[0].completed;
 store.todos[1].task = "Random todo " + Math.random();
 store.todos.push({ task: "Find a fine cheese", completed: true });
引用
目前我们创建出了可观察的对象,数组和基础类型。你可能奇怪,MobX里引用是什么样子的?我的状态数据结构能是一种图的形式吗?之前你可能意识到todos数组上有assignee字段。我们引入另一个store包含被赋予任务的人。
 var peopleStore = mobx.observable([
     { name: "Michel" },
     { name: "Me" }
 ]);
 observableTodoStore.todos[0].assignee = peopleStore[0];
 observableTodoStore.todos[1].assignee = peopleStore[1];
 peopleStore[0].name = "Michel Weststrate";
现在有两个独立的store了。一个是people一个是todos。给assignee赋值的时候我们仅仅是用引用。但变化也能被自动更新到TodoView上。有了Mobx,就不需要把数据先范式化,再写selector来保证视图得到更新。实际上数据在哪里存储也根本不重要。只要对象是可观察的,MobX将会跟踪它们。JavaScript的引用也能被跟踪。MobX也将会自动跟踪衍生计算出来的数值。
异步操作
由于Todo应用里所有都是从状态数据里衍生出来的,所以状态什么时候更改并不重要。下面的代码很直观,我们用pendingRequests来让界面显示当前的加载状态。一旦加载完成,更新todos并且减去pendingRequests。和之前的TodoList代码比较一下,看看pendingRequests是如何使用的。observableTodoStore.pendingRequests++;
setTimeout(function() {
observableTodoStore.addTodo('Random Todo ' + Math.random());
observableTodoStore.pendingRequests--;
}, 2000);

写在最后

这是一篇Mobx的入门文章,Mobx还有很多的高级功能没有涉及,比如跨多组件应用,跨多层级组件如何应用等,一起来慢慢学习。11dbf14a4d2e38f92ab3182e2d32c864.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值