三、常用API介绍及用法
1、@observable (定义变量状态):observable
用于定义可观察状态,观测的数据可以是数字、字符串、数组、对象等。
使用@observable定义的变量是可以被修改的,如果是常量或者不可再被修改的变量就可以不用@observable修饰
例:从mobx中引入observable, import {observable} from 'mobx'; 使用如下:
@observable count: number = 0; @observable tool: string = ""; sex: string = 'nan'; @observable classCurrentId: string = ''; @observable classDataList: any = [];
注意:被 observable
观测数据的修改是同步的,不像 setState 那样是异步。
2、@observer (观察者):被observer修饰的组件,将会根据组件内使用到的被observable修饰的state的变化而自动重新渲染
一般用在修饰类class前面,内部有可观察的变量的话(通过@observable定义的),添加@observer,即可实时观察数据变化,重新render
例:从mobx-react中引入observer,import {observer} from "mobx-react";
如下:只要store中的count变化了,下面这个Timer组件就会重新render,重新更新,这就是@observer的用途
import store from './mobx/store';//引入store @observer class Timer extends React.Component { render() { return ( <div>{store.count}</div> ) } };
3、reaction (监听):组件中通过reaction监听store数据变化,变化之后在组件内部作出相关响应
可以监听一个store变量,也可以监听多个store变量,如下所示
监听一个的例子:
// 监听classCurrentId的变化,newClassId为最新的数据 reaction(() => store.mobTest.classCurrentId, (newClassId) => { //dosomething }) // 监听classDataListLength变化, newClassDataList为最新的数据 reaction(() => store.mobTest.classDataListLength, (newClassDataList) => { //dosomething });
监听多个变量例子:
/** 监听screenMode和ClassType.type的变化,下面newData为最新数据,可以通过 newData.mode, newData.type获取最细的数据 /* reaction(() => ({ mode: store.home.screenMode, type: store.ClassType.type }), newData => { //doSomething })
4、toJS():将观察者对象转换成正真定义的格式,如将观察者数组转换成数组,将观察者对象转换成对象
如:在store中定义了一个可修改的变量为数组格式:@observable answerList: any[] = []; 我们在获取最新answerList数据的时候,拿到的不是真正意义上的数组,是被观察着数组,这时候我们必须通过toJS转换一下就可以了
例子:
// answerList为store中定义的被观察的数组,使用reaction监听的时候,通过toJS转换成真正的数组 reaction(() => toJS(store.answer.answerList), newValue => { //doSomething })
5、@inject (注入):注入store中的方法和变量,然后组件里就可以直接通过this.props.xxx的方式使用其他store中的变量或方法
使用inject注入要注意,需要在跟组件将App组件使用Provider包裹,如下
import { Provider } from 'mobx-react'
//store为定义的mobx引入的store文件 class App extends Component{ render(){ return( <Provider store={store}> <ToDoApp/> </Provider> ) } }
组件中使用案例:
import store from '../mobx/store'; //直接引入store interface IProps { store?: any; //通过App传入的store } interface IState { } @inject('store');//注入所有store class PokeButton extends React.Component<IProps, IState> { //doSomething 组件里面就可以通过 this.props.store.xxx获取任意store数据 }
6、@computed (计算属性):使用@computed修饰的方法,内部只要有一个变量变化,就会重新计算,会在依赖的状态发生变化时会重新运行
注意点:
computed 值会被缓存:每当读取 computed 值时,如果其依赖的状态或其他 computed 值未发生变化,则使用上次的缓存结果,以减少计算开销。
computed 值会惰性计算:只有 computed 值被使用时才重新计算值。反之,即使 computed 值依赖的状态发生了变化,但是它暂时没有被使用,那么它不会重新计算。
例子:
//store中定义 @computed get classDataListLength() { return this.classDataList.length; }
使用:classDataListLength变化时执行
// 监听mobx 班级选中列表元素变化 reaction(() => store.mobTest.classDataListLength, (newClassDataList) => { //doSomething });
7、autorun(自定义反应):用于定义响应函数,并在定义时立即执行一次。以后,每当依赖状态发生变化时,autorun
自动重新运行。
当你想创建一个响应式函数,而该函数本身永远不会有观察者时,可以使用 mobx.autorun
。这通常是当你需要从反应式代码桥接到命令式代码的情况,例如打印日志、持久化或者更新UI的代码。当使用 autorun
时,所提供的函数总是立即被触发一次,然后每次它的依赖关系改变时会再次被触发。相比之下,computed(function)
创建的函数只有当它有自己的观察者时才会重新计算,否则它的值会被认为是不相关的。
经验法则:如果你有一个函数应该自动运行,但不会产生一个新的值,请使用autorun
。其余情况都应该使用 computed
。Autoruns 是关于 启动效果 (initiating effects) 的 ,而不是产生新的值。如果字符串作为第一个参数传递给 autorun
,它将被用作调试名。
传递给 autorun 的函数在调用后将接收一个参数,即当前 reaction(autorun),可用于在执行期间清理 autorun。
就像@observer 装饰器/函数,autorun 只会观察在执行提供的函数时所使用的数据。
var numbers = observable([1,2,3]); var sum = computed(() => numbers.reduce((a, b) => a + b, 0)); var disposer = autorun(() => console.log(sum.get())); // 输出 '6' numbers.push(4); // 输出 '10' disposer(); numbers.push(5); // 不会再输出任何值。`sum` 不会再重新计算。
8、@action(动作):用于定义状态修改操作
常用案例:只有通过@action才可以修改store数据
import {observable, action} from 'mobx'; class AnswerData { @observable currentState: string = ''; @observable micList: any[] = []; @observable teaStatus: string = 'set'; @observable timer: number = 0; timerId: any = null; startTimer = () => { this.timerId = setInterval(() => { this.timer += 1 }, 1000); } @action changeState (state: string) { this.currentState = state; } @action setList (l: any[]) { this.micList = l; this.startTimer(); } @action teaQuizEnd = () => { this.teaStatus = 'reStart'; } } export const answer = new AnswerData();
异步操作处理:
@action
只会对当前运行的函数做出反应,对于一些不在当前函数中调用的回调是无效的,例如 setTimeout
回调、Promise
的 then
或 async
语句。这些回调如果修改了状态,也应该用 action
包裹起来。以下方法可处理该问题:
修改状态的语句用 runInAction
包裹起来,runInAction包裹执行完成之后再修改
class Store { @observable githubProjects = [] @observable state = "pending" // "pending" / "done" / "error" @action async fetchProjects() { this.githubProjects = [] this.state = "pending" try { const projects = await fetchGithubProjectsSomehow() const filteredProjects = somePreprocessing(projects) // await 之后,再次修改状态需要动作: runInAction(() => { this.state = "done" this.githubProjects = filteredProjects }) } catch (error) { runInAction(() => { this.state = "error" }) } } }
用法:
action(fn) action(name, fn) @action classMethod() {} @action(name) classMethod () {} @action boundClassMethod = (args) => { body } @action(name) boundClassMethod = (args) => { body } @action.bound classMethod() {} @action.bound(function() {})
注意:action只能影响正在运行的函数,而无法影响当前函数调用的异步操作
@action createRandomContact () { superagent.get('https://randomuser.me/api/').set('Accept', 'application/json') .end(action("createRandomContact-callback", (error: any, results: any) => { if (error) { console.error(error); } else { console.log(results) } })); }
在 end 中触发的回调函数,被 action 给包裹了,这就很好验证了上面的那句话,action 无法影响当前函数调用的异步操作,而这个回调毫无疑问是一个异步操作,所以必须再用一个 action 来包裹住它,这样程序才不会报错。
如果你使用 async function
来处理业务,那么我们可以使用 runInAction
这个 API 来解决问题。
import {observable, action, runInAction} from 'mobx'; class Store { @observable name = ''; @action load = async () => { const data = await getData(); runInAction(() => { this.name = data.name; }); } }
你可以把 runInAction
有点类似 action(fn)()
的语法糖,调用后,这个 action
方法会立刻执行。