438 vuex:基本使用,传参,vue和vuex的配合使用,Vue脚手架3,使用Vuex改版 TodoMVC,actions,mapGetters,mapMutations,mapActions,反向代理解决跨域

一 、Vuex的介绍

vuex 是什么?

  • 状态管理工具
  • 状态:即数据, 状态管理就是管理组件中的data数据
  • Vuex 中的状态管理工具, 采用了 集中式 方式统一管理项目中组件之间需要通讯的数据 【共享的数据。】
  • [看图]

如何使用

  • 最佳实践 : 只将组件之间共享的数据放在 vuex 中, 而不是将所有的数据都放在 vuex 中
  • 也就是说: 如果数据只是在组件内部使用的, 这个数据应该放在组件中, 而不要放在 vuex
  • vuex 中的数据也是 响应式 的, 也就是说: 如果一个组件中修改了 vuex 中的数据, 另外一个使用的 vuex 数据的组件, 就会自动更新 ( vuex 和 localstorage的区别)

什么时候用 ?

  • 官网
  • 说明: 项目体量很小, 不需要使用 vuex, 如果项目中组件通讯不复杂, 也不需要使用 vuex
  • 只有写项目的时候, 发现组件通讯多, 组件之间的关系复杂, 项目已经无法继续开发了, 此时, 就应该使用 vuex

二、 Vuex的基本使用

vuex的基本使用

  • 安装 : npm i vuex
  • 引入 : 引入 vuex 之前一定要先引入 vue
    • <script src="./node_modules/vuex/dist/vuex.js"></script>
  • 实例化 store
    • store 仓库 , 获取数据和操作数据都要经过 store
    • const store = new Vuex.Store()
  • 操作数据
    • 获取数据 : store.state.num
    • 操作数据 : store.state.num = 300
    • 虽然 store.state.count = 300 可以修改值 , 但是vuex 也有严格模式,
    • 添加严格模式 : strict : true,
  • 使用 mutations 【相当于methods】
    • 注册 : mutations : {}
    • increament(state) { state.count = 20; }
    • 默认第一个参数永远是 state
    • 触发事件 : store.commit('increament')

01-vuex的基本使用.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <!-- 
       1. 安装 npm i vuex 
       2. 引入 
       - vuex里面引入了vue的api 引入vuex之前必须 要引入vue 
       3. 实例化 
     -->

    <script src="./vue.js"></script>
    <script src="./node_modules/vuex/dist/vuex.js"></script>

    <script>
        // 实例化
        // store 仓库 管理数据(查询/修改数据 都要经过 vuex)
        const store = new Vuex.Store({
            // 严格模式
            strict:  true, 

            // 状态 :  数据 相当于 data
            state:  {
                name:  '小春'
            }, 
            // mutations 相当于 methods
            mutations:  {
                // 第一个参数 :  state
                updateName(state) {
                    state.name = '大春'
                }
            }
        })

        //2. 修改数据
        // store.state.name = '大春'
        store.commit('updateName')

        //1. 获取数据
        console.log(store.state.name)

        /**
            * 注意点
            1. 虽然 修改数据 store.state.name ='大春', 确实改变了数据,但是 vuex 有严格模式
            2. do not mutate(修改) vuex store state outside mutation handlers.
               在 mutation 处理函数 外面 不能修改 store>state里的数据
        */
    </script>
</body>

</html>

vuex的传参

  • **触发事件 : **

  • # 传参最好传一个对象,  多个值查看方便 
    store.commit('increament',   {
        num:  400
    })
    
  • 事件

  • # payload 载荷
    increament(state,   payload) {
    	state.count = payload.num
    }
    

02-传参.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <script src="./vue.js"></script>
    <script src="./node_modules/vuex/dist/vuex.js"></script>
    <script>
        // 实例化仓库
        const store = new Vuex.Store({
            // 严格模式
            strict:  true, 

            // state 状态
            state:  {
                name:  '小春春'
            }, 
            // mutations
            mutations:  {
                // 修改数据
                updateName(state,  payload) {
                    // payload 负载 数据
                    state.name += payload.num
                }
            }
        })

        //2. 修改数据
        // 参数1 :  方法名
        // 参数2 :  参数数据
        // store.commit('updateName',  777)
        // 传一个对象
        store.commit('updateName',  {
            num:  888
        })

        //1. 获取数据
        console.log(store.state.name)
    </script>
</body>

</html>

vue和vuex的配合使用

需求 : 有个h1显示数字的标题, 点击按钮累加数字

  • 先用vue做出来效果
  • 再用vuex和vue配合使用
    • 实例化vuex的store
    • 实例化vue
    • 把store挂载到vue上
  • 操作数据
    • h1展示数据 : <h1>{{ $store.state.num }}</h1>
    • 点击触发事件修改数据 : this.$store.commit('addNum')
    • addNum(state) { state.num++ }

03-vue和vuex的配合使用.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <title>Document</title>
</head>

<body>
    <!-- 
        需求 :  有个h1显示数据, 点击按钮, 累加
        1. vue处理
        2. 改造为vuex
    -->

    <div id="app">
        <h1>{{ $store.state.num }}</h1>
        <button @click="fn">按钮</button>
    </div>
    
    <script src="./vue.js"></script>
    <script src="./node_modules/vuex/dist/vuex.js"></script>
    <script>
        // 实例化 vuex 的仓库
        const store = new Vuex.Store({
            // 严格模式
            strict:  true, 
            // 状态
            state:  {
                num:  200
            }, 
            mutations:  {
                // 累加数据
                increamentNum(state) {
                    state.num += 1
                }
            }
        })

        // 实例vue
        const vm = new Vue({
            el:  '#app', 
            store, 
            data:  {}, 
            methods:  {
                fn() {
                    // this.$store.state.num = 300
                    this.$store.commit('increamentNum')
                }
            }
        })
    </script>
</body>

</html>

三、Vue脚手架3.0

官网 : https: //cli.vuejs.org/zh/guide/installation.html

安装

  • 安装脚手架 2.x : npm i vue-cli -g
  • 安装脚手架 3.x : npm i -g @vue/cli
  • 检测脚手架版本号 : vue -V / --version

创建一个项目

  • 命令 : vue create vuex-todos (可视化 vue ui)
  • 可以选择默认配置或者手动配置
  • 开发运行 : npm run serve
  • 发布构建 : npm run build

四、使用Vuex改版 TodoMVC

  • 初始化项目
  • 拷贝模板(todomvc-app-template)里的结构(section部分)和样式 (node_modules里的)
  • 组件化
    • 创建 todo-header.vue、todo-list.vue、todo-footer.vue + scaf结构
    • app.vue 中 导入 : import todoheader from "./components/todo-header.vue";
    • 注册 : components: { todoheader , todolist , todofooter }
    • 使用 : <todofooter></todofooter>
  • 配置 vuex 管理 list
    • 创建文件夹 store/store.js
    • 安装 vuex
    • 引入
    • vue安装vuex : Vue.use(Vuex)
    • 实例store, 并且导出 store
    • main.js 中引入并挂载到 vue 上
  • 列表展示
  • 删除任务
  • 添加任务
  • 修改任务
  • 修改状态
  • 计算属性(三个)
  • 清除已经完成的任务

五、如何使用 actions


  • 官网介绍
  • Action 类似于 mutation,不同在于:
    • Action 可以包含任意异步操作。
    • Action 提交的是 mutation,而不是直接变更状态。
  • mutaions 里只能使用同步, 不能出现异步 (演示删除任务 里使用setTimeout 会报错)
  • 演示1: actions 可以包含任意异步操作。 代码1
  • 演示2: actions 不能直接变更状态 , 代码2 会报错
  • 演示3 : actions 提交的是 mutation
# 都是 actions 里
//演示1 : 
setTimeout(() => {
    console.log('actions')
},   0)

// 演示2 : 报错
setTimeout(() => {
    context.state.list = context.state.list.filter(
        item => item.id != payload.id
    )
},   0)

// 演示3 :  提交 mutations
setTimeout(() => {
    context.commit('delTodo',   payload)
},   0)

六、常用的几个辅助函数

mapGetters 辅助函数

  • store.js 中的几个计算属性 :

  • let getters = {
      isFooterShow(state) {
        return state.list.length > 0
      },  
      itemLeftCount(state) {
        return state.list.filter(item => !item.done).length
      },  
      isClearShow(state) {
        return state.list.some(item => item.done)
      }
    }
    
  • 使用 mapGetters

    • todo-footer.vue 中 引入 : import { mapGetters } from "vuex";

    • 将 store 中的 getter 映射到局部计算属性

      computed: { ...mapGetters(["isFooterShow", "itemLeftCount", "isClearShow"]) }-

    • 使用

      • 以前通过属性 : <footer v-show="$store.getters.isFooterShow">
      • 现在通过辅助函数 : <footer v-show="isFooterShow">

mapMutations 辅助函数

# 写在 methods
# 映射
...mapMutations(["delTodo",   "updateTodo",   "changeState"]),  

 # 起别名 (防止当前所在的函数名和这个mutaions名一致,  会导致死循环)
...mapMutations({
    deltodo:  "delTodo",  
    updatetodo:  "updateTodo",  
    changestate:  "changeState"
}),   

# 以后使用  
  this.deltodo({id})  替代 :   this.$store.commit('delTodo',  { id })

mapActions 辅助函数

# 写在 methods
# 映射
...mapActions(["asyncDelTodo"]),  
# 起别名    
    ...mapActions({
        aDT:  "asyncDelTodo"
    }),  
        
# 使用别名
  this.aDT({ id }); 
# 如果没有起别名
  【可以在actions中直接通过commit触发mutations事件;也可以在模板页面中,通过dispatch触发mutations的事件。】
 this.asyncDelTodo({ id });  替换  this.$store.dispatch('asyncDelTodo',  {id})


App.vue

<template>
    <div id="app">
        <section class="todoapp">
            <!-- 头部 -->
            <todoHeader></todoHeader>

            <!-- 列表部分 -->
            <todoList></todoList>

            <!-- 底部 -->
            <todoFooter></todoFooter>
        </section>
    </div>
</template>

<script>
    // 引入三个子组件
    import todoHeader from "./components/todoHeader.vue";
    import todoList from "./components/todoList.vue";
    import todoFooter from "./components/todoFooter.vue";

    export default {
        name: "app",
        components: {
            todoHeader,
            todoList,
            todoFooter
        }
    };
</script>

<style>
</style>


main.js

import Vue from 'vue'
import App from './App.vue'
// 引入css
import './assets/base.css'
import './assets/index.css'
// 引入 仓库
import store from './store/store.js'

Vue.config.productionTip = false

new Vue({
    store,
    render: h => h(App)
}).$mount('#app')

//演示跨域
import axios from 'axios'

// https://douban.uieee.com/v2
// https://locally.uieee.com/categories
axios.get('/myapi/movie/in_theaters').then(res => {
    console.log(res)
})


store.js

// 引入 vuex
import Vue from 'vue'
import Vuex from 'vuex'

// 安装
Vue.use(Vuex)

// 抽离 state
const state = {
  list: [
    { id: 1, name: '吃饭', done: true },
    { id: 2, name: '睡觉', done: false },
    { id: 3, name: '打死春春', done: false }
  ]
}

// 抽离 mutations
const mutations = {
  // 添加任务
  addTodo(state, payload) {
    const id = state.list.length === 0 ? 1 : state.list[state.list.length - 1].id + 1
    // 添加
    state.list.push({
      id,
      name: payload.name,
      done: false
    })
  },
  // 删除任务
  delTodo(state, payload) {
    // setTimeout(() => {
    state.list = state.list.filter(item => item.id != payload.id)
    // }, 0)
  },
  // 修改状态
  changeState(state, payload) {
    //1. 根据id 查找当前的任务
    let todo = state.list.find(item => item.id == payload.id)

    //2. 状态取反
    todo.done = !todo.done
  },
  // 修改任务名称
  updateTodo(state, payload) {
    //1. 根据id找到对应的任务
    let todo = state.list.find(item => item.id == payload.id)
    //2. 修改任务
    todo.name = payload.name
  },
  // 清除完成
  clearCompleted(state) {
    // 过滤出来未完成的,重新赋值list
    state.list = state.list.filter(item => !item.done)
  }
}

// 抽离 getters (计算属性)
const getters = {
  // 底部的显示与隐藏
  isFooterShow(state) {
    return state.list.length > 0
  },
  // 剩余未完成的个数
  itemLeftCount(state) {
    return state.list.filter(item => !item.done).length
  },
  // clearCompleted 的显示与隐藏
  isClearCompletedShow(state) {
    return state.list.some(item => item.done)
  }
}

// 抽离 actions
const actions = {
  // 参数1 : context,类似store,所以有的人直接写store
  // 参数2 :
  asyncDelTodo(context, payload) {
    setTimeout(() => {
      // 在store.js的actions中,是写context.commit;在vue component中,是写this.$store.dispatch('异步函数名')
      context.commit('delTodo', payload)
    }, 0)
  }
}

// 实例化 仓库
const store = new Vuex.Store({
  // 严格模式
  strict: true,
  state,
  mutations,
  getters,
  actions
})

// 导出仓库
export default store




todoHeader.vue


import { loadavg } from 'os';
<template>
    <header class="header">
        <h1>todos</h1>
        <input
            class="new-todo"
            placeholder="What needs to be done?"
            autofocus
            @keyup.enter="addTodo"
            v-model="todoName"
        />
    </header>
</template>

<script>
    // 第一步 引入
    import { mapMutations } from "vuex";
    
    export default {
        data() {
            return {
                todoName: ""
            };
        },
        methods: {
            // 第二步 : 映射
            ...mapMutations(["addTodo"]),
            ...mapMutations({
                addtodo: "addTodo"
            }),

            // 添加任务
            addTodo() {
                console.log(this.todoName);
                // this.$store.commit('addTodo', {
                //   name: this.todoName
                // })
                this.addtodo({
                    name: this.todoName
                });

                this.todoName = "";
            }
        }
    };
</script>

<style>
</style>



todoList.vue


<template>
    <section class="main">
        <input id="toggle-all" class="toggle-all" type="checkbox" />
        <label for="toggle-all">Mark all as complete</label>
        <ul class="todo-list">
            <li
                :class="{ completed : item.done, editing : item.id == editId}"
                v-for="item in $store.state.list"
                :key="item.id"
            >
                <div class="view">
                    <input class="toggle" type="checkbox" :checked="item.done" @input="changeState(item.id)" />
                    <label @dblclick="showEdit(item.id)">{{ item.name }}</label>
                    <button @click="delTodo(item.id)" class="destroy"></button>
                </div>
                <input class="edit" :value="item.name" @keyup.enter="hideEdit" />
            </li>
        </ul>
    </section>
</template>

<script>
    import { mapMutations, mapActions } from "vuex";
    
    export default {
        data() {
            return {
                editId: -1
            };
        },
        methods: {
            // 将store>mutaions 里的 delTodo , 映射到当前的方法 【相当于methods有了"delTodo"、"updateTodo"、 "changeState"这些方法】
            ...mapMutations(["delTodo", "updateTodo", "changeState"]),
            // 起别名
            ...mapMutations({
                deltodo: "delTodo",
                changestate: "changeState"
            }),

            // 映射actions
            ...mapActions(["asyncDelTodo"]),

            // 删除任务
            delTodo(id) {
                // list 少一个

                // 1. commit => mutations => 同步
                // this.$store.commit('delTodo', { id })
                // this.deltodo({ id }) // 重名,死循环

                // 2. dispatch =>  actions => 异步 【可以在actions中直接通过commit触发mutations事件;也可以在模板页面中,通过dispatch触发mutations的事件。】
                // this.$store.dispatch('asyncDelTodo', { id })
                
                // 3.使用mapMutations映射过来的方法 【相当于methods有了 "asyncDelTodo"方法,所以直接用this调用。】
                this.asyncDelTodo({ id });
            },
            // 显示编辑状态
            showEdit(id) {
                this.editId = id;
            },
            // 隐藏编辑状态
            hideEdit(e) {
                this.updateTodo({
                    id: this.editId,
                    name: e.target.value
                });

                // this.$store.commit('updateTodo', {
                // id: this.editId,
                // name: e.target.value
                // })

                this.editId = -1;
            },
            // 修改状态
            changeState(id) {
                // this.$store.commit('changeState', { id })
                // this.changeState({id})
                this.changestate({ id });
            }
        }
    };
</script>

<style>
</style>


todoFooter.vue


<template>
    <footer class="footer" v-show="isFooterShow">
        <!-- This should be `0 items left` by default -->
        <span class="todo-count">
            <strong>{{ itemLeftCount }}</strong> item left
        </span>

        <!-- Hidden if no completed items are left ↓ -->
        <button
            @click="clearCompleted"
            v-show="isClearCompletedShow"
            class="clear-completed"
        >Clear completed</button>
    </footer>
</template>

<script>
    // 注意,解构,mapGetters要用{}包裹
    import { mapGetters } from "vuex";

    export default {
        methods: {
            clearCompleted() {
                this.$store.commit("clearCompleted");
            }
        },
        computed: {
            // 将vuex>store 里面的几个getters 属性 映射到 组件内的计算属性
            // 以后使用 就可以把 下面几个当成当前组件的计算属性用了
            ...mapGetters(["isFooterShow", "itemLeftCount", "isClearCompletedShow"])
        }
    };
</script>

<style>
</style>



笔记

初始化项目

  1. 安装脚手架 : npm i @vue/cli -g

  2. 创建项目 : vue create vuex-todos

    默认

  3. 运行项目 : npm run serve

  4. 把没用的删除


把 todos 模板拿过来

  1. 拷贝 模板中
  2. 拷贝 node_modules > base.css/index.css
  3. 在 main.js 中引入 css

组件化 改造

  1. 创建 todoHeader.vue 把 头部标签代码拷贝过去
  2. 引入组件
  3. 注册组件
  4. 使用组件

配置 vuex

  1. 安装 : npm i vuex

  2. 创建 文件 store.js

    router/router.js
    store/store.js

  • 引入
  • 实例化 store
  • 导出
  1. 挂载到 vue 实例上
  2. 准备数据 list

列表展示

  1. v-for 遍历
  2. 处理名称
  3. 处理选中状态
  4. 处理横线

添加任务

抽离 state 和 mutations

删除任务

修改任务

  1. 显示编辑状态
  2. 编辑任务
  3. 隐藏编辑状态

修改状态

底部显示与隐藏 + 剩余未完成个数 + 是否显示清除完成

  1. 使用 getters 类似 vue 的计算属性
  2. 使用 : v-show='$store.getters.isFooterShow'

清除完成的


actions

mutations 里面不能放异步操作
异步操作应该放在 actions

  1. actions 里面可以放任意异步操作
setTimeout(() => {
  console.log('我是异步的咋地地')
}, 0)
  1. actions 不能直接修改状态 , 提交 mutations
 asyncDelTodo(context, payload) {
    setTimeout(() => {
      context.commit('delTodo', payload)
    }, 0)
  }

几个辅助函数

辅助函数 1-mapGetters

mapGetters 简化 getters
store里面的getters属性 映射到当前组件内的计算属性

  1. 第一步 : import { mapGetters } from 'vuex'
  2. 第二步 :
computed : {
  ...mapGetters(['isFooterShow','XXXXX'])
}
  1. 第三步 :使用
v-show='isFooterShow'

辅助函数 2-mapMutations

简化 mutaions

  1. 引入 import { mapMutations } from 'vuex'
  2. 映射
methods : {
  ...mapMutations(['delTodo']),
  ...mapMutations({
    deltodo :'delTodo'
  })
}
  1. 使用
this.deltodo({ id })

辅助函数 03-mapActions

简化 actions
将 store> actions 里面的方法 , 映射到当前组件内的方法

  1. 引入
  2. 映射
  3. 使用

跨域问题:反向代理

反向代理

一 : 说明

  • 解决跨域问题的方式 :
    • JSONP == > 只能处理 get 方式
    • CORS ==> 处理自己的服务器
    • 反向代理 ==> 也很常用
  • 说明
    1. 演示跨域问题
    2. 反向代理的原理
    3. 脚手架vue-cli 生成的项目中如何使用反向代理

二、 演示跨域问题

测试真实请求接口 : https://api.douban.com/v2/movie/in_theaters

  1. todo-vuex 里的 app.vue 中 的js 代码区域演示

  2. 安装 axios

  3. 代码 :

    // 演示跨域问题
    /* eslint-disable */
    import axios from 'axios';
    
    axios.get('https://api.douban.com/v2/movie/in_theaters').then(res => {
      console.log(res)
    })
    

    ~

  4. 报错 :

    Access to XMLHttpRequest at 'https://api.douban.com/v2/movie/in_theaters' from origin 'http://localhost:8080' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
    
  5. 报错原因

    - 项目运行在  http://localhost:8080
      //  I  Your application is running here: http://localhost:8080  
    - 发送ajax请求 : //域名是 https://api.douban.com/v2/movie/in_theaters
    - 出现跨域问题
    

三 、反向代理的原理


四、演示

  • 修改 config/index.js 配置文件
proxyTable: {
  '/myapi': {
    // 代理的目标服务器地址:https://api.douban.com/v2/movie/in_theaters
    // /myapi/movie/in_theaters
    target: 'https://api.douban.com/v2',
    pathRewrite: { '^/myapi': '' },
    secure: false, // 设置https
    changeOrigin: true // 必须设置该项
  }
},

  • 最终代码

    // axios.get('https://api.douban.com/v2/movie/in_theaters').then(res => {
    axios.get("http://localhost:8080/api/movie/in_theaters").then(res => {
      console.log(res);
    });
    
    
  • 最终配置 cli2.x :

    proxyTable: {
      '/myapi': {
        // 代理的目标服务器地址:https://api.douban.com/v2/movie/in_theaters
        // /myapi/movie/in_theaters
        target: 'https://api.douban.com/v2',
        pathRewrite: { '^/myapi': '' },
    
        // 设置https
        secure: false,
        // 必须设置该项
        changeOrigin: true
      }
    },
    
    
  • 最终配置 3.X

    • 根目录下 新建一个 vue.config.js
    • 拷贝如下代码
    module.exports = {
      devServer: {
        proxy: {
          '/myapi': {
            // 代理的目标服务器地址:https://api.douban.com/v2/movie/in_theaters
            // /myapi/movie/in_theaters
            target: 'https://api.douban.com/v2',
            pathRewrite: { '^/myapi': '' },
    
            // 设置https
            secure: false,
            // 必须设置该项
            changeOrigin: true
          }
        }
      }
    }
    
    // 使用
    axios.get('http://localhost:8080/myapi/movie/in_theaters').then(res => {
      console.log(res)
    })
    
    axios.get('/myapi/movie/in_theaters').then(res => {
      console.log(res)
    })
    
    

    ~

  • 重新启动 : npm run dev

posted on 2020-04-13 10:13  冲啊!  阅读(422)  评论(0编辑  收藏  举报

导航