准备:
- 此文章里的demo以create-react-app,vue-cli的默认demo为基础。
- mobx需要es7的装饰器语法,所以需要对项目进行一定的修改与配置,需要先eject。如果不eject也可以,但是不推荐,方法也在后面引用的链接里。详见create-react-app + mobx其中@observer装饰器报错
demo功能很简单,效果及说明如下:
说明:
- full name = first name + last name,且响应式显示
- 点reset按钮,清空所有数据
- fist name,last name都为空时,full name显示提示语
分析:
变量:firstName,lastName
方法:getFullName、doReset(为了方便,react还要封装一个setInputValue方法,让input监听onchange时调用)
react、vue、react + mobx三种方案的代码实现对比:
react实现:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
class App extends Component {
constructor(props) {
super(props);
this.state = {
firstName: '',
lastName: ''
}
}
setValue(key, event) {
this.setState({
[key]: event.target.value
});
}
getFullName() {
let { firstName, lastName } = this.state;
if (!firstName && !lastName) {
return 'Please input your name!'
} else {
return firstName + ' ' + lastName;
}
}
doReset() {
this.setState({
firstName: '',
lastName: ''
})
}
render() {
const st = this.state;
const fullName = this.getFullName();
return (
<div>
<h1>This is normal react!</h1>
<p>First name: <input type="text" value={st.firstName} onChange={e => this.setValue('firstName', e)} /></p>
<p>Last name: <input type="text" value={st.lastName} onChange={e => this.setValue('lastName', e)} /></p>
<p>Full name: {fullName}</p>
<p><button onClick={() => { this.doReset() }}>Reset</button></p>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();
vue实现:
<template>
<div>
<h1>This is vue!</h1>
<p>First name: <input type="text" v-model="firstName" /></p>
<p>Last name: <input type="text" v-model="lastName" /></p>
<p>Full name: {{fullName}}</p>
<p><button @click="doReset">Reset</button></p>
</div>
</template>
<script>
export default {
data() {
return {
firstName: "",
lastName: ""
};
},
computed: {
fullName() {
const { firstName, lastName } = this;
if (!firstName && !lastName) {
return "Please input your name!";
} else {
return firstName + " " + lastName;
}
}
},
methods: {
doReset() {
this.firstName = "";
this.lastName = "";
}
}
};
</script>
对比:
- 模板,vue因为有自己的语法糖,所以较简洁,这点优势不算大。但有一点需要注意一下,react要生成fullName需要在render里调用getFullName方法,这个其实有点让render不太“纯”(只负责渲染,业务逻辑尽量少)了。而vue引入了computed的概念,值得借鉴。
- 方法,按照性质来说,方法可以分为两类,就像vue把方法分成了computed和methods。而react的所有方法都写在一起,当方法特别多的时候,比较难区分,虽然可以通过一些约定来形成一些规范,但是实在难保证效果,vue的思想值得借鉴。
- 赋值,react需要setState,vue则直接赋值(起码在语法上是),虽然觉得react这种写法可以使数据在变化时,特别容易识别,但是写起来还是有点麻烦。。。
区别还能说出来更多,但是最主要的就是这几点,那么当react遇到mobx时会发生什么呢?
mobx + react实现:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import { observable, computed, useStrict, action } from 'mobx';
import { observer } from 'mobx-react';
useStrict(true)
class VM {
@observable firstName = '';
@observable lastName = '';
@computed get fullName() {
const { firstName, lastName } = this;
if (!firstName && !lastName) {
return 'Please input your name!'
} else {
return firstName + ' ' + lastName;
}
};
@action.bound
setValue(key, event) {
this[key] = event.target.value;
}
@action.bound
doReset() {
this.firstName = '';
this.lastName = '';
}
}
@observer
class App extends Component {
render() {
const vm = this.props.vm;
return (
<div>
<h1>This is mobx-react!</h1>
<p>First name: <input type="text" value={vm.firstName} onChange={e => vm.setValue('firstName', e)} /></p>
<p>Last name: <input type="text" value={vm.lastName} onChange={e => vm.setValue('lastName', e)} /></p>
<p>Full name: {vm.fullName}</p>
<p><button onClick={vm.doReset}>Reset</button></p>
</div>
);
}
}
ReactDOM.render(<App vm={new VM()} />, document.getElementById('root'));
registerServiceWorker();
那几个@xxxx虽然可能不太知道是啥意思,但是也差不多能猜出来八九不离十。仔细对比代码后可以发现:
- 模板,照react变化不大,只是少了fullName之类的computed方法,让render变得更干净。而且render只与props有关,所以结合无状态组件来使用,就只剩下模板了,非常干净,这个大家可以想象一下。
- 方法,可以看到VM部分,其实就相当于vue的VM(scrpit部分),而且分为了数据,计算属性,方法三个部分。还是比较清晰的。
- 最后,没有了setState!没有了setState!没有了setState!自动监听了啊有木有!声明时一个@observable就解决了啊有木有!活脱脱变成vue的双胞胎了啊有木有!
总结:
vue作者尤雨溪如是说:
结合react可以直接引用外部模块这点(通常是保存公共变量或方法的模块,这点vue需要靠vuex来实现,所以在这点上,react是更方便的),mobx可以替代redux作为项目的数据处理方案,可参考:【译】Redux 还是 Mobx,让我来解决你的困惑!
最后,如果有必要,再封装一些语法糖,甚至可以做到比vue还要简洁。当然,会额外增加学习成本,这个就需要自己权衡了。
最后附上用无状态组件实现的代码,大家可以品一下
父组件index
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import { App, AppVM } from './App';
ReactDOM.render(<App vm={new AppVM()} />, document.getElementById('root'));
registerServiceWorker();
子组件App,无状态组件
// App.js
import React from 'react';
import { observer } from 'mobx-react';
import { observable, computed, action } from 'mobx';
export const App = observer(({ vm }) => (
<div>
<h1>This is mobx-react!</h1>
<p>First name: <input type="text" value={vm.firstName} onChange={e => vm.setValue('firstName', e)} /></p>
<p>Last name: <input type="text" value={vm.lastName} onChange={e => vm.setValue('lastName', e)} /></p>
<p>Full name: {vm.fullName}</p>
<p><button onClick={vm.doReset}>Reset</button></p>
</div>
))
export class AppVM {
@observable firstName = '';
@observable lastName = '';
@computed get fullName() {
const { firstName, lastName } = this;
if (!firstName && !lastName) {
return 'Please input your name!'
} else {
return firstName + ' ' + lastName;
}
};
@action.bound
setValue(key, event) {
this[key] = event.target.value;
}
@action.bound
doReset() {
this.firstName = '';
this.lastName = '';
}
}
我故意把render放在了上面,怎么样,这么看是不是更像vue了点~