Vue.js and Vuex

Vue.js - 显示模板+数据监听

一个简单而典型的HTML应用的主要任务如下:

  1. 获取数据 (Model)
  2. 根据数据渲染HTML显示 (View)
  3. 根据业务逻辑更新数据
  4. 更新对应的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由一个树形结构的组件集合构成,提供的好处是“分而治之”。

但是“难逃宿命”的是很多时候组件还是需要跟其外部的数据进行交互。

该问题的解决,大的思路有两个

  1. 全局变量
  2. 参数传递

借助全局变量

借用略作修改的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;
    }
  }
});

mutationsactions

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")
);

相关资源