• Vue3.x 从零开始(五)—— Router + Vuex + TypeScript 实战演练(上)


    前面的几篇文章已经大致介绍了 Vue 3 的常用 API,现在综合起来做一个实战演练

    配合完整代码食用更香哦,项目地址:https://github.com/wisewrong/test-vue3-demo

    一、初始化

    首先通过 Vue-CLI 创建一个 Vue 3 项目,详细流程可以参考《Vue3.x 从零开始(一)》

    vue create test-vue3-demo

    勾选 TypeScript、Router、Vuex,版本选用 Vue 3.x,其他的选项可以自行选择,拿不准就直接回车选择默认

    初始化完成后的项目是这样的:

    store 目录用来维护基于 Vuex 开发的状态仓库

    router 目录维护基于 vue-router 开发的路由配置

    main.ts 是项目的入口文件,在这里将 Router 和 Vuex 载入项目中:

    二、头部导航( Router )

    首先需要创建头部导航 <header> 组件,header 属于公共组件,可以放到 components 目录下

    header 上有 Home 和 About 两个页面的导航入口,点击导航可以跳转到对应的页面

    这个功能可以通过 vue-router 提供的 <router-link> 来实现

    <router-link> 是经过封装的 <a> 标签,它需要接收一个路由地址 to,类似于 <a> 标签的 href

    <router-link to="/home">Home</router-link>

    如果目标路由地址配置了组件,就能在父组件的 <router-view> 中渲染对应组件

     

    路由的配置文件是 src/router/index.ts ,可以配置路由信息,包括路由地址和对应的组件

    不过 <router-link> 只适合导航菜单这种【只需要跳转页面,不需要做其他操作】的场景

    更多的时候我们需要在函数中进行路由跳转,这时候可以使用  this.$router.push() 

    三、登录框弹窗( teleport + slot )

    首先来完成弹窗组件 <modal>

    从业务上来讲,这个弹窗组件是在 <header> 上打开的,也就是说 <header> 会是 <modal> 的父组件

    如果按照传统开发组件的方式,<modal> 会渲染到父组件 <header> 的 DOM 节点下

    但从交互的层面来说,弹窗是一个全局性的强交互,组件应该渲染到外部,比如 body 标签

    在 Vue 3 中提供了一个新的解决方案 teleport

    在组件中用 <teleport> 组件包裹需要渲染到外部的模板,然后通过 to 指定渲染的 DOM 节点

    <teleport to="body">
        <div>
            <!-- 组件内容  -->
        </div>
    </teleport>

    to 接收一个可以被 querySelector 识别的字符串参数,用于查找目标 DOM 节点,该 DOM 节点必须在组件外部

    这里将 modal 组件渲染到了body 标签

     

    上面的代码还用到了插槽 <slot> 

    这个标签允许父组件向子组件插入自定义的模板内容,在 <modal> 组件中可以让父组件编辑弹窗的内容

    如果组件中需要配置多个 <slot> 标签,还可以用 name 来给 <slot> 命名

    <div class="modal-header">
      <slot name="header">
        <!-- 这里是 slot-header 的默认模板 -->
        <span class="modal-title">{{title}}</span>
        <button class="modal-close"></button>
      </slot>
    </div> 

    然后在父组件中通过 <template v-slot:name> 向指定的 slot 插入内容

    <template v-slot:header>
      <div>
        这里是 slot-header 的内容
      </div>
    </template>

    四、完成弹窗表单( $refs + Vuex )

    接下来开发登录窗的表单组件 <sign-in-form>

    组件的内容十分简单,就是两个输入框 <input />,不多介绍,重点在于获取表单数据

    由于这个 <form> 组件是 <header> 的子组件,所以我们需要在 <header> 获取 <form> 的数据并提交

    Vue 中可以通过 ref 属性获取自定义组件的实例

    比如上面的代码就在 <sign-in-form> 组件上指定了 ref="signInForm"

    然后就能在 header 组件中通过 this.$refs.signinForm 获取到表单组件的实例,并直接使用它的 methods 或者 data

    // 没有找到适合 $ref 的类型断言,只好用 any
    const data = (this.$refs.signInForm as any).getValue(); // getValue 是 signInForm 组件中的 methods

    现在获取到了登录信息,正常来说需要用登录信息请求登录接口,如果用户名和密码正确,接口会返回用户信息

    这里我们就跳过请求接口的过程,直接把登录信息当做用户信息

    用户信息对于整个项目来说是一个共用信息,我们可以选择暂存在 localStorage 或 sessionStorage 中,也可以使用 Vuex 来管理

    如果在使用 Vue-CLI 创建项目时勾选了 Vuex,就能在 src/store/index.ts 中维护公共变量和方法

    然后在组件中通过 this.$store 来使用 Vuex 提供的 API


    Vuex 中有 State、Getter、Mutation、Action、Module 五个核心属性

    其中 State 就像是 Vue 组件中的相应数据 data,Getter 类似于计算属性 computed

    然后 Mutation 和 Action 都可以看做 methods,区别在于:

    Mutation 是同步函数,用来更新 state(在严格模式下只能通过 mutation 来更新 state)

    // Vuex
    const state = {
      user: 'wise wrong'
    };
    
    const mutations = {
      // 所有 mutation 的第一个参数都是 state,后面的参数在调用时传入
      updateUser(state, payload) {
        state.user = payload;
      }
    };
    
    // 组件中通过 commit 来调用 mutations
    export default {
      // ...
      methods: {
        handler() {
          this.$store.commit('updateUser', 'new user');
        }
      },
    };

    而 Action 可以看做 Mutation 的父级,用来提交 Mutation,而且可以包含异步函数

    // Vuex
    const actions = {
      // action 的第一个参数是 context,其中包含 commit,用来调用 mutation
      fetchUser(context, payload) {
        fetch('/api', payload)
          .then((res) => {
            // 调用 mutation
            context.commit('updateUser', res.data);
          })
          .catch()
      }
    }
    
    // 组件中通过 dispatch 来调用 action
    export default {
      // ...
      methods: {
        handler() {
          this.$store.dispatch('fetchUser', {id: 123});
        }
      },
    };

    回到我们的项目上来,由于我们用的是 TypeScript,所以需要提前定义 state 的类型

    // 用户信息
    export interface UserState {
      user: string;
      password: string;
    }
    
    // state 的根类型
    export interface RootState {
      userInfo: UserState;
    }

    然后创建 state.ts 和 mutations.ts

    然后在组件中通过 commit 调用 mutation 以更新用户信息:

    由于在 Vuex 4 中删除了对全局属性 $store 的类型支持,所以上面的截图中 $store 被标红,代码也无法运行

    为解决该问题,可以在 src 目录下创建一个 shims-vuex.d.ts 文件,手动声明 $store

    import { Store } from 'vuex';
    
    declare module "@vue/runtime-core" {
      interface ComponentCustomProperties {
        $store: Store;
      }
    }

    但我更推荐使用 Vue 提供的辅助函数 map

    对于 state,我们可以在 computed 中使用 mapState 将需要的 state 映射到当前组件

    import { mapState } from 'vuex';
    
    export default {
      // ...
      computed: {
        // 组件本身的计算属性
        localComputed () { /* ... */ },
    
        // 使用对象展开运算符将 state 混入当前组件
        ...mapState([
          'userInfo', // 以数组的形式传入 state 的键名
        ])
      },
    
      methods: {
        test() {
          // 以计算属性的形式使用 state
          console.log(this.user);
        }
      }
    }

    同样的,对于 mutations 可以通过 mapMutations 混入 methods 中

    import { mapMutations } from 'vuex';
    
    export default {
      // ...
      methods: {
        // 组件本身的方法
        test() {
          // 以 methods 的形式调用 mutation
          this.updateUserInfo();
        },
        ...mapMutations([
          // 混入名为 updateUserInfo 的 mutation
          'updateUserInfo',
        ])
      }
    }

    上面 this.$store.commit 的调用方式就可以替换成:

    到这里为止已经具备了一个简单的 Vue 项目的雏形

    接下来会打造一个综合性的 Todo List,并真正用上 Composition API

  • 相关阅读:
    Mysql Error Code: 1175. You are using safe update mode and you tried to update a table without a WHERE that uses a KEY column To disable safe mode
    vs2012+ winform+.net4.0发布如何在xp上运行
    ubuntu下手动配置apache2.4.12
    mysql连接错误解决(ERROR 2049 (HY000): Connection using old (pre-4.1.1) authentication protocol ref used (client option 'secure_auth' enabled))
    位运算取绝对值
    位运算两数交换
    java mysql prepareStatement模糊查询like使用注意
    idea14远程调试linux下的tomcat
    web视频播放插件:Video For Everybody
    cmd杀死进程
  • 原文地址:https://www.cnblogs.com/wisewrong/p/13839396.html
Copyright © 2020-2023  润新知