一、TodoList
1.1安装依赖
安装相关依赖:
npm install --save-dev webpack
npm install --save-dev babel-loader babel-core babel-preset-es2015
npm install --save-dev vue-loader
npm install --save-dev css-loader
npm install --save-dev vue-template-compiler
npm install --save-dev stylus stylus-loader
npm install --save vue
npm install --save vuex
1.2配置虚拟服务器
【第一步:安装依赖】
npm install -g json-server
【第二步:json-server的服务器启动】
启动需要json文件作为支撑,新建data.json文件做服务数据:
{
"mapList" : [
{"id":1 ,"title" : "吃饭", "done" : false},
{"id":2 ,"title" : "睡觉", "done" : false},
{"id":3 ,"title" : "做饭", "done" : false},
{"id":4 ,"title" : "扫地", "done" : false}
]
}
启动命令json-server命令:
json-server 数据文件路径 -s 静态化目录 -p 8080
json-server www/data/data.json -s ./www -p 8080
-s是 --static
-p是 --port
最终没有使用webpack-dev-server模拟服务器
配置webpack.config.js文件:
const path = require('path'); const {VueLoaderPlugin} = require('vue-loader'); //最新版webpack需要引入插件 module.exports = { //程序的入口文件 entry: "./www/app/app.js", //程序的出口(打包的文件) output : { //打包文件输出的路径 path : path.resolve(__dirname, "www/dist"), //打包文件的名称 filename : "all.js", // publicPath:"/public" //这是对webpack-dev-server的配置,配置虚拟路径 }, //让webpack监听变化,自动打包 watch : true, mode : "development", //配置webpack的模块插件 module:{ //关于模块的配置规则 rules : [ { //模块规则(配置 loader、解析器等选项) test : /.js$/, //解析的时候匹配到的都是js文件 include: [ path.resolve(__dirname, "www/app") //翻译什么文件夹 ], exclude: [ path.resolve(__dirname, "node_modules") //不翻译什么文件夹 ], loader : "babel-loader", options : { presets : ["es2015"] } }, { test: /.vue$/, include: [ path.resolve(__dirname, "www/app") //翻译什么文件夹 ], exclude: [ path.resolve(__dirname, "node_modules") //不翻译什么文件夹 ], loader: 'vue-loader', options :{ loaders : { stylus:'vue-style-loader!css-loader!stylus-loader' } } }, { test:/.css$/, use: ['vue-style-loader','css-loader'] } // { // test: /.styl(us)?$/, // use: [ // 'vue-style-loader', // 'css-loader', // 'stylus-loader' // ] // } ] }, resolve: { alias: { //配置别名 'vue$': 'vue/dist/vue.esm.js' // 用 webpack 1 时需用 'vue/dist/vue.common.js' } }, //最新版webpack需要引入插件 plugins : [ new VueLoaderPlugin() ] }
【第三步:访问接口】
获取所有的数据:GET
增加数据:POST
得到某一条数据:GET
删除某一条:DELETE
更新原始数据:PUT (更新条目的所有的属性)、PATH(更新条目的某一个属性)
自带分页:page(第几页)limit(每页有多少条)
http://localhost:8080/mapList 返回.json文件所有数据(数组)
http://localhost:8080/mapList/1 返回id=1的数据,采用这种路径风格id必须为小写(数组)
http://localhost:8080/mapList/?Name=lst 返回Name=lst的用户数据(数组)
分页查询:参数为 _start, _end, _limit,并可添加其它参数筛选条件,如: http://localhost:8080/mapList?_start=6&_limit=3 http://localhost:8080/mapList?_start=3&_end=6 排序:参数为_sort, _order http://localhost:8080/mapList?_sort=id&_order=asc http://localhost:8080/mapList?_sort=user,views&_order=desc,asc 操作符:_gte, _lte, _ne, _like _gte大于,_lte小于, _ne非, _like模糊查询,q关键词查询 http://127.0.0.1:8080/mapList/?q=吃
1.3 TodoList
查询数据
相关步骤:
在created生命周期中发出一个GETALL的异步命令,去actions里面发异步请求。
App.vue
<script> export default { created(){ this.$store.dispatch("GETALL"); } } </script>
main.js
import Vue from "vue"; import Vuex from "vuex"; import App from "./App.vue"; Vue.use(Vuex); const store = new Vuex.Store({ state : { todos : [] }, mutations: { GETALL(state,payload){ // state.todos.push(payload) state.todos = payload; } }, actions :{ async GETALL({commit}){ //请求数据库中的数据,然后存储到state的todos中 var data = await fetch("/maplist").then(data=>data.json()); //发出commit()命令,并且将参数当载荷传递过去 commit("GETALL", data); } } }); new Vue({ el : "#app", store, render:(h)=>h(App) })
异步请的数据回调成功后,将数据以载荷的形式发送到mutations,再去修改state 的数据
state全局仓库有了数据,就可以在App.vue进行循环渲染。
App.vue
<template> <div> <ul> <li v-for="item in todos"> {{item.title}} </li> </ul> </div> </template> <script> export default { created(){ this.$store.dispatch("GETALL"); }, computed : { todos(){ return this.$store.state.todos } } } </script>
下一步:需要将li标签拆分成组件:
App.vue
<template> <div> <ul> <li is="TodoLi" v-for="item in todos" :item="item"></li> </ul> </div> </template> <script> import TodoLi from "./components/TodoLi.vue"; export default { created(){ this.$store.dispatch("GETALL"); }, computed : { todos(){ return this.$store.state.todos } }, components:{ TodoLi } } </script>
TodoLi.vue
<template> <li>{{item.title}}</li> </template> <script> export default { props : ["item"] } </script>
删除数据
接下来写增删改查业务,推荐先写删除,因为删除值需要id
TodoLi.vue
<template> <li> <span>{{item.title}}</span> <button @click="del(item.id)">删除</button> </li> </template> <script> export default { props : ["item"], methods:{ del(id){ this.$store.dispatch("DEL", {id}) } } } </script>
const store = new Vuex.Store({ state : { todos : [] }, mutations: { GETALL(state,payload){ // state.todos.push(payload) state.todos = payload; }, DEL(state, payload){ // 根据id删除state的数据 state.todos = state.todos.filter(item=>{ return item.id != payload.id; }) } }, actions :{ async GETALL({commit}){ //请求数据库中的数据,然后存储到state的todos中 var data = await fetch("/maplist").then(data=>data.json()); //发出commit()命令,并且将参数当载荷传递过去 commit("GETALL", data); }, async DEL({commit},payload){ //向服务器发出删除请求,删除data.json中的数据 var data = await fetch("/maplist/" + payload.id, { method:"DELETE", }).then(data => data.json()); commit("DEL", payload); //删除全局state仓库的数据 }, } });
新增业务
App.vue
<template> <div> <div> <input type="text" v-model.trim="txt"> <button @click="add">新增</button> </div> <ul> <li is="TodoLi" v-for="item in todos" :item="item"></li> </ul> </div> </template> <script> import TodoLi from "./components/TodoLi.vue"; export default { data(){ return { txt : '' } }, methods :{ add(){ if(this.txt == '') return; //发出新增的异步请求 this.$store.dispatch(ADD, {id: new Date() - 0, title:this.txt, done:false}) this.txt = "" } }, components:{ TodoLi } } </script>
main.js
actions : { //新增数据 async ADD({ commit }, payload) { //向data.json文件中插入数据 var data = await fetch("/maplist/", { method: "POST", headers:{'Content-type' : 'application/json'}, body: JSON.stringify(payload) }).then(data => data.json()); commit("ADD", data); //新增数据到state,影响视图更新 } }, mutations : { ADD(state, payload) { state.todos.push(payload); } }
修改数据
TodoLi.vue
<template> <li> <span v-if="!isShow" @dblclick="showInput">{{item.title}}</span> <input type="text" v-if="isShow" v-model="item.title" @blur="hideInput(item)" v-focus> <button @click="del(item.id)">删除</button> </li> </template> <script> export default { props : ["item"], data(){ return{ isShow:false } }, methods:{ del(id){ this.$store.dispatch("DEL", {id}) }, showInput(){ //双击显示input this.isShow = !this.isShow; }, hideInput(item){ console.log(item) //失去焦点隐藏input,并且发送CAHNGETITLE命令修改title内容 this.isShow = !this.isShow; this.$store.dispatch("CHANGETITLE",item) } }, directives:{ //自定义组件指令,自动获取焦点 focus :{ inserted(el){ el.focus(); } } } } </script>
main.js
const store = new Vuex.Store({ state : { todos : [] }, mutations: { CHANGETITLE(state, payload) { // 写法1 // state.todos.forEach(item=>{ // if (item.id == payload.id){ // item.title = payload.title; // } // }) // 写法2 state.todos = state.todos.map(item => { if(item.id == payload.id) { return payload } return item; }) } }, actions :{ //修改请求 async CHANGETITLE({commit}, payload) { var data = await fetch("/maplist/" + payload.id, { method: "PATCH", headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }).then(data => data.json()); commit("CHANGETITLE", data); //虽然实现了修改,但是为了完整性还是写上 }, } });
修改任务事件:当前任务是否做了,需要一个复选框
todoli.vue
<li> <input type="checkbox" v-model="item.done" ref="cbox" @click="changeDone(item)"> <spanv-if="!isShowInput"@dblclick="showInput" :class="{cur:item.done}">{{item.title}}</span> </li> <script> methods:{ changeDone(item){ console.log(this.$refs.cbox.checked); // 这里使用的是ref钩子 这样能得到 复选框的状态 // 提交的时候 只需要提交 id 复选框的状态就行了 this.$store.dispatch("CHANGEDONE",{ id : item.id, done : this.$refs.cbox.checked }) } } </script> <style> .cur{text-decoration: line-through;} </style>
app.js修改done和修改title的方式方法都一样,只要改方法名即可。
const store = new Vuex.Store({ mutations:{ CHANGEDONE(state, payload){ state.todos = state.todos.map(item => { if(item.id == payload.id){ return payload } return item }) } }, actions:{ async CHANGEDONE({commit},payload){ var data = await fetch("/mapList/" + payload.id,{ "method" : 'PATCH', "headers":{ 'Content-Type':"application/json" }, body:JSON.stringify({done:payload.done}) }).then(res => res.json()); commit("CHANGEDONE",data) }, } });
筛选按钮:计算已做未做事项
筛选按钮:计算已做未做事项
app.js
const store = new Vuex.Store({ state:{ todos:[] }, mutations:{ ... }, actions:{ ... }, getters:{ yizuo:function(state){ return state.todos.filter(item => { return item.done == true //true表示已做 }) }, weizuo:function(state){ return state.todos.filter(item => { return item.done == false //false表示未做 }) } } });
App.vue
<template> <div> <div> <input type="text" v-model.trim="txt"> <button @click="add">新增</button> </div> <ul> <li v-for="item of todos" is="TodoLi" :item="item"></li> </ul> <div> 全部:{{$store.state.todos.length}}个事项<br> 已做:{{$store.getters.yizuo.length}}个完成事项<br> 未做:{{$store.getters.weizuo.length}}个代办事项<br> </div> <div> <button @click="all">查看全部</button> <button @click="yizuo">查看已做</button> <button @click="weizuo">查看未做</button> </div> </div> </template> <script> import todoli from './components/TodoLi.vue' export default { data() { return { txt: '', state: 'all' } }, computed: { todos() { if(this.state == 'all'){ return this.$store.state.todos }else if(this.state == 'yizuo'){ return this.$store.getters.yizuo }else if(this.state == 'weizuo'){ return this.$store.getters.weizuo } } }, methods: { all() { this.state = 'all' }, yizuo() { this.state = 'yizuo' }, weizuo() { this.state = 'weizuo' } } } </script>
所有业务都已经完成,但是由于组件和数据太乱,下面开始拆分store。
二、拆分store
我们都知道vuex是vue用来集中管理状态的容器(管理全局的状态),实现不同组件之间相互的数据访问。我们说一下vuex拆分store以及多模块管理。如果一个项目非常大的话状态就会非常的多,如果不进行分类处理,所有的状态都维护在一个state里面的话,状态管理就会变得非常的混乱,这样非常不利于项目的后期维护。现在前端推崇模块化开发,为的就是提高开发效率和维护效率,避免重复工作。那么vuex是怎么样解决这个问题的呢?这个时候我们今天要讲的主角modules就要闪亮登场了。
第一步:将main.js中的store拆分到一个js文件中。
export const storeObj = { state:{ ... }, mutations:{ ... }, actions:{ ... }, getters:{ ... } }
将store中的index.js在main.js中引入
import {storeObj} from "./store/index.js"; const store = new Vuex.Store(storeObj);
第二步:拆分state、mutations、actions、getters
给他们分别创建单独的js文件,默认暴露
export default { }
可以将我们定义的这些对象加入到Vuex的store中
在store中index.js用import引入:
import state from "./state.js"; import mutations from "./mutations.js"; import actions from "./actions.js"; import getters from "./getters.js"; export const storeObj = { state:{ ...state }, mutations:{ ...mutations }, actions:{ ...actions }, getters:{ ...getters } }
由于没装翻译“...”对象解构语法,会报错:
npm install --svae-dev babel-plugin-transform-object-rest-spread
配置webpack.config.js
{ test: /.js?$/, //解析的时候匹配到的都是js loader: "babel-loader", //翻译字典 options: { presets: ["es2015","ES2015"], plugins: ["transform-object-rest-spread"] } },
配置完成后就可以识别“...”解构了。
至此,还没拆完,使用常量代替mutations集合actions的type名字
新建一个types.js文件,在里面存常量(大写字母的方法)
注意:把这些常量放到单独的文件中可以让你的代码合作者对整个app包含mutation和actions一目了然。
用不用常量代替取决于你--在需要多人协作的大型项目中,这会很有帮助,但是如果你不喜欢,完全可以不这么做。
export const GETALL = "GETALL"; export const DEL = "DEL"; export const ADD = "ADD"; export const CHANGETITLE = "CHANGETITLE"; export const CHANGEDONE = "CHANGEDONE";
暴露常量的名字后,分别在mutations和actions的js文件中使用这些来代替:
mutations.js
import {GETALL,DEL,ADD,CHANGETITLE,CHANGEDONE} from "./types.js"; export default { [GETALL](state, payload) { }, [DEL](state, payload) { }, [ADD](state, payload) { }, [CHANGETITLE](state, payload) { }, [CHANGEDONE](state, payload) { } }
actions.js
import {GETALL,DEL,ADD,CHANGETITLE,CHANGEDONE} from "./types.js"; export default { async [GETALL]({ commit }) { }, async [DEL]({ commit }, payload) { }, async [ADD]({ commit }, payload) { }, async [CHANGETITLE]({ commit }, payload) { }, async [CHANGEDONE]({ commit }, payload) { } }
注意:上方mutations和actions中的[types.ADD]写法,因为types.ADD是一个对象,所以不能直接当做函数名来写,需要用到ES2015风格的计算属性命名功能来使用一个常量作为函数名,才能使用。
https://vuex.vuejs.org/zh/guide/mutations.html
三、异步请求数据(跨域)
3.1 Ajax不能跨域
比如在Apache中演示:
<html> <head> <title>Document</title> </head> <body> <script src="jquery-3.3.1.min.js"></script> <script> $.get("http://127.0.0.88/a.txt" , function(data){ console.log(data); }); </script> </body> </html>
试图在127.0.0.1请求127.0.0.88/a.txt的