Vue.js and Vuex
- Vue.js - https://vuejs.org/
- Vuex - https://vuex.vuejs.org/
Vue.js - 显示模板+数据监听
一个简单而典型的HTML应用的主要任务如下:
- 获取数据 (Model)
- 根据数据渲染HTML显示 (View)
- 根据业务逻辑更新数据
- 更新对应的HTML显示
静态数据渲染
传统的HTML做法大致如下:
var data = {count: 1}; // 数据或者状态,Model
var element = document.getElementById("counter"); // 图形展示。View
element.innerHTML = "count is <em>" + data.count + "</em>"; // 渲染显示
如果按照Vue的写法,就是:
<p id="counter">count is <em>{{count}}</em></p>
<script>
var app = new Vue({
el: "#counter",
data: {
count: 1
}
})
</script>
这个时候看起来,Vue提供了一种模板功能。可以根据数据注入到模板代码中。
动态数据渲染
传统的HTML做法大致如下,每次数据有更新的时候,触发显示部分的更新:
function updateData(){ // 更新数据
data.count++;
}
function updateView(){ // 更新显示
element.innerHTML = "count is <em>" + data.count + "</em>";
}
setInterval(function(){ // 控制器 Controller
updateData(); // 更新数据
updateView(); // 更新显示
}, 1000); // 各种Action触发更新需求
从工程实践角度看,开发人员希望之专注于关键的数据部分。用代码描述就是希望简化成
setInterval(function(){
data.count++; // 只负责更新数据。其它的不想管
}, 1000);
这种需求的实现,需要对数据data
进行监听。只要在data发生变化时,重新调用显示更新逻辑即可。
Vue提供了这种数据监控的能力。
Component
GUI的构成通常抽象为一个树形结构的组件(Component)集合。
上面counter的例子就可以看作是一个组件。因此用Vue可以描述一个小组件是很自然的推论。
Vuex - 数据管理
GUI由一个树形结构的组件集合构成,提供的好处是“分而治之”。
但是“难逃宿命”的是很多时候组件还是需要跟其外部的数据进行交互。
该问题的解决,大的思路有两个
- 全局变量
- 参数传递
借助全局变量
借用略作修改的Vuex网站上的例子
const store = {
state: {
count: 0
}
};
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return store.state.count
}
}
}
- 组件直接通过全局变量
store
访问数据 computed
的使用是为了实现对数据的监听
借助参数传递
全局变量的缺点是,你只能有一个实例。
如果需要多个实例,这个“全局”的变量就需要作为参数传递给需要的地方。
某种程度上可以说,Vuex解决的问题主要就是如何传递这个全局参数。
组件通过其自己的computed
数据借助this.$store
来引用这个全局参数。
Vue.use(Vuex); // 启用注入机制
const Counter = {
template: `<div>{{ count }}</div>`,
computed: {
count () {
return this.$store.state.count; // 主要区别就在这里
// 全局变量的版本类似于 window.store.state.count
}
}
}
const app = new Vue({
el: '#app',
// provide the store using the "store" option.
// this will inject the store instance to all child components.
store,
components: { Counter },
template: `
<div class="app">
<counter></counter>
</div>
`
})
mapState 和 getters
mapState
使用在组件(Component)里,用来“简化”组件里的"computed"数据定义。
getters
是用在Store
里,用于增加store
层面的"computed"数据。
两者都可以认为是一种语法糖。用来减少代码数量。
const store = new Vuex.Store({
state: { count: 1 },
getters: {
onemore: function(state){
return state.count+1;
}
}
});
mutations
和 actions
commit & dispatch
Vuex约定大家不直接修改store里面的数据,而是通过一个函数间接操作。
这一方面给了在store
层面监听数据改变的机会,另一方面可以借助函数名让其字面意思更明显。
Vuex约定改变数据是同步(Synchronous)的。这有助于调试和理清代码逻辑。如此一来就需要增加一个概念用于异步(Asynchronous )的数据改变。
mutations
用于定义同步的数据改变函数- 对应的调用方法是
store.commit()
- 对应的调用方法是
actions
用于定义异步地数据改变函数- 对应的调用方法是
store.dispatch()
- 对应的调用方法是
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment (state) {
state.count++; // 同步地改变数据
}
},
actions: {
increment (context) {
setTimeout(function(){
context.commit('increment')
}, 1000); // 异步地改变数据
}
}
})
函数的声明与定义
上面关于全局参数传递的问题,传统的函数编程大多时候其实就是直接传递。
int method_1(Store store, ...){
method_2(store, ...);
}
int main(){
Store store = new Store();
method_1(store, ...);
}
上面所展示的Vuex里面的做法实际上并没有显得多么简化——实际上是显得更复杂。
究其原因,应该是因为Vue本质上有点类似于只保留了函数的声明,而移除了定义部分。
用户声明模板是什么样子,声明数据是什么子,但不定义具体如何从数据到模板的绑定——这部分交由框架来解决。
如果让用户来定义函数内容的实现,那么用户自然会负责全局参数的传递。回归到传统的编程模式里。
React - 在函数里写模板
React实际上走的就是在函数里直接写模板的路线。
在这种模式下,全局参数的传递就是自顶而下逐层传递,是很自然的事情。
class Counter extends React.Component {
constructor(props) {
super(props); // propts可以认为就是那个全局参数
this.state = { count: 0 };
}
render() {
return (
<div>
count is <em> {this.state.count} </em>
</div>
);
}
tick() {
this.setState(state => ({
count: state.count + 1
}));
}
}
ReactDOM.render(
<Counter count="1" />,
document.getElementById("counter")
);
相关资源
- 模板绑定和框架
- Vue.js - https://vuejs.org/
- React - https://reactjs.org/
- 数据状态管理
- Vuex - https://vuex.vuejs.org/
- Redux - https://redux.js.org/
- Flux - https://facebook.github.io/flux/
- Elm - http://elm-lang.org/
- RxJS - https://github.com/ReactiveX/RxJS