Vue advanced road: vue3.2-setup syntactic sugar, combined API, state library Pinia summary

Hello everyone, my name is Dongdong acridine. What you are seeing now is the vue advanced series. If you think it is good, you can like and collect it. If you like my friends, you can also add a follow duck.

The vue advanced series includes the following:

Vue advanced road: 8 ways of component communication, have you figured it out?

Vue advanced road: what is the life cycle execution process of parent-child components?

The road to advanced vue: five core concepts of vuex, it is enough to read this article.

Vue advanced road: front-end technology is changing with each passing day, the era of vue3.0 has come...

Vue advanced road: Ding, what are the differences between Vue2 and Vue3? please check!

Foreword:

I haven't finished learning vue3.0, and vue3.2 is here again. Can you still learn? (manually funny)

There are the following differences in syntax between vue3.2 and vue3.0:
vue3.0 version: variables and methods must be returned before they can be used.
vue3.2 version: only need to add the setup attribute to the script tag, no need to return variables and methods, the code is more concise.

This article will focus on the syntax of the vue3.2 version. If you have not fully touched vue3, you can go to my article to warm up first. Front-end technology is changing with each passing day, and the era of vue3.0 has come...

Create project:

vite scaffolding creation project: vue3+vite2+ts

npm create vite@latest
复制代码

1. Component structure

<script setup lang="ts">
//直接在script标签上添加setup属性
//...

</script>

<template>
//vue2只支持一个根节点,vue3支持多个根节点
//...

</template>

<style scoped>
// 支持CSS变量注入v-bind(color)
//...

</style>
复制代码

二.data

<script setup lang="ts">
import { reactive, ref, toRefs } from 'vue'

  // ref声明响应式数据,用于声明基本数据类型
  const name = ref('东东吖')
  // 修改
  name.value = '小鲁班'

  // reactive声明响应式数据,用于声明引用数据类型
  const state = reactive({
    age: 24,
    sex: '男'
  })
  // 修改
  state.age = 24
  
  // 使用toRefs解构  template可直接使用{{name}}、{{sex}}
  const {age, sex} = toRefs(state)
  
</script>

<template>
  <h1>{{ name }}</h1>
  <h1>{{ state.age }}</h1>
  <h1>{{ sex }}</h1>
</template>

<style scoped>

</style>
复制代码

Three.method

<script setup lang="ts">
import { reactive, toRefs } from 'vue'

  // reactive声明响应式数据,用于声明引用数据类型
  const state = reactive({
    age: 24,
    sex: '男'
  })
  // 使用toRefs解构  template可直接使用{{name}}、{{sex}}
  const {age, sex} = toRefs(state)

   // 声明method方法
  const changeAge =()=>{
    state.age+=1
  }
  
</script>

<template>
  <h1>{{ age }}</h1>
  
  <!-- 调用方法 -->
  <button @click="changeAge()">点我</button>
</template>

<style scoped>

</style>
复制代码

Four.computed

<script setup lang="ts">
import { ref,computed } from 'vue'

 // ref声明响应式数据,用于声明基本数据类型
  const count = ref(1)

  //computed获取双倍count' 
  const doubleCount =computed(()=>{
    return count.value*2
  })

  
</script>

<template>
  <h1>{{ count }}</h1>
  <h1>{{doubleCount}}</h1>

</template>

<style scoped>

</style>
复制代码

5. Father to Son

//父组件:

<script setup lang="ts">
// 引入子组件(组件自动注册)
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld msg="东东吖" />
</template>

<style>

</style>
复制代码
//子组件:

<script setup lang="ts">
<script setup lang="ts">
// import { defineProps } from 'vue'
// defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineProps: true】

// 声明props
const props = defineProps({
  msg: {
    type: String,
    default: "",
  },
});
</script>

<template>

{{props.msg}}

<!-- 可省略props -->
  <h1>{{ msg }}</h1>

</template>

<style scoped>
</style>



复制代码

6. Son to father

//父组件:

<script setup lang="ts">
import { reactive } from "vue";
// 引入子组件(组件自动注册)
import HelloWorld from "./components/HelloWorld.vue";

const state = reactive({
  msg: "我是父组件原本的值",
});

const changeMsg = (val) => {
  state.msg = val;
};
</script>

<template>
  <HelloWorld :msg='state.msg' @changeMsg="changeMsg" />
</template>

<style>
</style>
复制代码
//子组件:

<script setup lang="ts">
import { defineEmits } from "vue";

// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

// 声明props
const props = defineProps({
  msg: {
    type: String,
    default: "",
  },
});

// 声明emit
const emit = defineEmits(["changeMsg"]);

// 声明子传父事件
const changeMsg = () => {
  emit("changeMsg", "我是子组件传过来的值");
};
</script>

<template>

{{props.msg}}

<!-- 可省略props -->
  <h1>{{ msg }}</h1>


<!-- 调用子传父事件 -->
  <button @click="changeMsg">点我</button>

</template>

<style scoped>
</style>


复制代码

7. Prototype chain binding and component usage

//main.ts

// 创建vue实例
const app=createApp(App)
// 获取原型
const prototype = app.config.globalProperties
// 绑定参数
prototype.name = '我是挂载在全局上的属性'
复制代码
//组件内获取使用

//引入
import { getCurrentInstance } from "vue";
// 获取原型
const { proxy } = getCurrentInstance();
// 输出
console.log(proxy.name);

复制代码

Eight. Any component communication mitt.js

  • Vue2.x uses EventBus for component communication, while Vue3.x recommends using mitt.js.

9. Two-way binding v-model

//父组件:

<script setup lang="ts">
import { reactive } from "vue";
// 引入子组件(组件自动注册)
import HelloWorld from "./components/HelloWorld.vue";

const state = reactive({
  msg: "我是父组件原本的值",
  age:24
});

</script>

<template>
<div>父组件:</div>
<div>{{state.msg}}</div>
<input type="text" v-model="state.msg">

  <HelloWorld
   v-model:msg='state.msg'
   v-model:age="state.age"
   />
</template>

<style>
</style>

复制代码
//子组件:

<script setup lang="ts">
import { defineEmits } from "vue";

// import { defineEmits, defineProps } from 'vue'
// defineEmits和defineProps在<script setup>中自动可用,无需导入
// 需在.eslintrc.js文件中【globals】下配置【defineEmits: true】、【defineProps: true】

// 声明props
const props = defineProps({
  msg: {
    type: String,
    default: "",
  },
  age: {
    type: Number,
    default: 0,
  },
});

// 声明emit
const emit = defineEmits(["updata:msg", "updata:age"]);
</script>

<template>
<div>子组件:</div>
<input type="text" v-model="msg">

<!-- 可省略props -->
  <h1>{{ msg }}</h1>
  <h1>{{age}}</h1>

</template>

<style scoped>
</style>


复制代码

10. nextTick

<script setup lang="ts">
import { reactive ,nextTick } from "vue";
// 引入子组件(组件自动注册)
import HelloWorld from "./components/HelloWorld.vue";

const state = reactive({
  msg: "我是父组件原本的值",
});

 // 调用nextTick
nextTick(()=>{  
 console.log("nextTick执行了...");
 state.msg="我是nextTick执行后的值"
 
})

</script>

<template>
  <HelloWorld
   v-model:msg='state.msg'
   />
</template>

<style>
</style>
复制代码
子组件:
<script setup lang="ts">
import { defineProps,nextTick } from "vue";
// 声明props
const props = defineProps({
  msg: {
    type: String,
    default: "",
  },
});

 // 调用nextTick
nextTick(()=>{  
 console.log("nextTick执行了...");
 
})


</script>

<template>
  <h1>{{ msg }}</h1>
</template>

<style scoped>
</style>

复制代码

Eleven. Slots

//子组件:

<script setup lang="ts">
  import { useSlots, reactive } from 'vue'
  const state = reactive({
    name: '东东吖',
    age: '25岁'
  })
  
  const slots = useSlots()
  // 匿名插槽使用情况
  const defaultSlot = reactive(slots.default && slots.default().length)
  console.log("defaultSlot",defaultSlot) // 1
  // 具名插槽使用情况
  const titleSlot = reactive(slots.title && slots.title().length)
  console.log("titleSlot",titleSlot) // 3
</script>

<template>
<!-- 匿名插槽 -->
  <slot/>
  <!-- 具名插槽 -->
  <slot name='title'/>
   <!-- 作用域插槽 -->
  <slot name="footer" :scope="state" />


</template>

<style scoped>
</style>

复制代码
//父组件:

<script setup lang="ts">
import HelloWorld from "./components/HelloWorld.vue";

</script>

<template>
<HelloWorld>
   <!-- 匿名插槽 -->
    <span>我是默认插槽</span>
    <!-- / 具名插槽 -->
    <template #title>
      <h1>我是具名插槽1</h1>
      <h1>我是具名插槽2</h1>
      <h1>我是具名插槽3</h1>
    </template>
    <!-- 作用域插槽 -->
    <template #footer="{ scope }">
      <footer>作用域插槽——姓名:{{ scope.name }},年龄{{ scope.age }}</footer>
    </template>
  </HelloWorld>
</template>

<style>
</style>

复制代码
//页面展示情况:

我是默认插槽

# 我是具名插槽1

# 我是具名插槽2

# 我是具名插槽3

作用域插槽——姓名:东东吖,年龄25岁
复制代码

12. Routing useRoute and useRouter

//新建router/index.ts

import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
const routes: Array<RouteRecordRaw> = [
  { 
    path: '/',
    redirect: '/home'
  },
  { 
    path: '/home',
    name: 'home',
    component: () => import('../pages/home/Index.vue'),
    meta: {
      showFooter: true,
      requireAuth: false,
    }
  },
  { 
    path: '/about',
    name: 'about',
    component: () => import( '../pages/about/Index.vue'),
    meta: {
      showFooter: true,
      requireAuth: false,
    }
  },


];

const router = createRouter({
  history: createWebHashHistory(),
  routes,
});

export default router;
复制代码
//在main.ts将路由router注册
import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index.ts'


const app=createApp(App)
app.use(router).mount('#app')


复制代码
//在页面打印结果

import { useRouter, useRoute } from "vue-router";

// 必须先声明调用
const router = useRouter();
const route = useRoute();

// 路由信息
console.log("router", router);
console.log("route",route);
复制代码

Thirteen. Routing guard

// 路由守卫

router.beforeEach((to, from, next) => {
 console.log("to",to);
 console.log("from",from);
 next()
})

复制代码

14. Life cycle

Mapping between options API lifecycle options and composition API

  • beforeCreate -> use setup()
  • created -> use setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeUnmount -> onBeforeUnmount
  • unmounted -> onUnmounted
  • errorCaptured -> onErrorCaptured
  • renderTracked -> onRenderTracked
  • renderTriggered -> onRenderTriggered
  • activated -> onActivated
  • deactivated -> onDeactivated

fifteen.store

  • vuex

Vuex in Vue3 no longer provides auxiliary function writing. Partners who want to learn vuex can move to my article. The road to advanced vue: five core concepts of vuex, it is enough to read this article.

  • Pinia

What vue3 recommends more is pinia. On November 24, 2021, Youda announced on Twitter: It Pinia has officially become the official state library of Vue, which means  Pinia ,  Vuex 5let’s fully embrace pinia!

①Basic usage process

//下载pinia

npm install pinia -S
复制代码
//main.ts

import { createApp } from 'vue'
import App from './App.vue'
import router from './router/index.ts'

// 引入pinia
import { createPinia } from 'pinia'

// 创建vue实例
const app=createApp(App)

// 创建 Pinia 实例
const pinia = createPinia()

// 注册挂载到vue实列
app.use(router).use(pinia).mount('#app')

复制代码
// store/index.ts

import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore =  defineStore('main',{
    // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
    // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
    // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
    state:()=>{
        return {
            info:"hello,东东吖,我是Pinia"
        }
    },
    getters:{},
    actions:{}
})
复制代码
//组件内使用

<template>
    <div>
   <h1>{{ mainStore.info}}</h1>
    </div>
</template>

<script setup lang="ts">
import { useMainStore } from "../../store/index.ts";
const mainStore = useMainStore();


</script>

<style scoped>

</style>
复制代码

Destructuring access to data in state

// store/index.ts
import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore =  defineStore('main',{
    // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
    // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
    // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
    state:()=>{
        return {
            info:"hello,东东吖,我是Pinia",
            count:10
        }
    },
    getters:{},
    actions:{}
})
复制代码
//组件内使用

<template>
  <h1>{{ mainStore.count }}</h1>
  <h1>{{ mainStore.info }}</h1>
  <hr />
  <h1>{{ count }}</h1>
  <h1>{{ info }}</h1>
  <p>
    <button @click="alertData">修改数据count</button>
  </p>
</template>

<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);
const alertData = () => {
  mainStore.count += 10
}
</script>

<style>
</style>

复制代码

How to modify data in state (in actions and components)

// 一般的修改

<template>
  <h1>{{ mainStore.count }}</h1>
  <h1>{{ mainStore.info }}</h1>
  <hr />
  <h1>{{ count }}</h1>
  <h1>{{ info }}</h1>
  <p>
    <button @click="alertData">修改数据count</button>
  </p>
</template>

<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);


const alertData = () => {
  // 方式一:最简单的方法,如下
  // 解构后更改方式
//   count.value += 10
  // 结构前更改方式
//   mainStore.count += 10
  // 方式二:若要同时修改多个数据,建议使用$patch来实现批量更新,在内部做了优化
//   mainStore.$patch({
//     count: mainStore.count + 1,
//     info: "hello"
//   })
  // 方式三:更好的批量更新方法,通过$patch传递一个函数来实现,这里的state就是useMainStore容器中的state
  mainStore.$patch(state => {
    state.count += 10
    state.info = "pinia批量更新"+state.count
  })
}
</script>

<style>
</style>

复制代码
//通过actions修改
// store/index.ts

import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore =  defineStore('main',{
    // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
    // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
    // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
    state:()=>{
        return {
            info:"hello,东东吖,我是Pinia",
            count:10
        }
    },
    getters:{},
    
// store/index.ts
// 类似于vue2组件的methods,用于封装业务逻辑,修改state
// // 注意:不能使用箭头函数来定义actions,因为箭头函数绑定外部的this
actions:{
    changeState (){
        this.count += 10
        this.info = "actions修改数据"
    },
    changeStates (num:number){
        this.count += num + 2
        this.info = "actions修改数据"
    }
}

})

复制代码
//组件内使用

<template>
  <h1>{{ mainStore.count }}</h1>
  <h1>{{ mainStore.info }}</h1>
  <hr />
  <h1>{{ count }}</h1>
  <h1>{{ info }}</h1>
  <p>
    <button @click="alertData">修改数据count</button>
  </p>
</template>

<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);

const alertData = () => {
  // 方式四:通过 actions 来修改数据
  mainStore.changeState()
  mainStore.changeStates(10)
}

</script>

<style>
</style>

复制代码

Use of getters

// store/index.ts
import { defineStore } from 'pinia'
// 1. 定义容器、导出容器
// 参数1:容器的ID,必须是唯一的,后面Pinia会把所有的容器挂载到根容器
// 参数2:一些选项对象,也就是state、getter和action
// 返回值:一个函数,调用即可得到容器实例

export const useMainStore =  defineStore('main',{
    // 类似于Vue2组件中的data,用于存储全局状态数据,但有两个要求
    // 1. 必须是函数,目的是为了在服务端渲染的时候避免交叉请求导致的数据状态污染
    // 2. 必须是箭头函数,这样是为了更好的 TS 类型推导
    state:()=>{
        return {
            info:"hello,东东吖,我是Pinia",
            count:10
        }
    },

    // 类似于组件的computed,用来封装计算属性,具有缓存的功能
    getters:{
        // 函数接收一个可选参数:state状态对象
       count10(state){
           return state.count += 10
       },
       count20(state){
           return this.count += 20
       },
       // 若使用this.count,则必须指明返回数据的类型
       count11():number{
           return this.count += 11
       }
   },
    
// store/index.ts
// 类似于vue2组件的methods,用于封装业务逻辑,修改state
// // 注意:不能使用箭头函数来定义actions,因为箭头函数绑定外部的this
actions:{
    changeState (){
        this.count += 10
        this.info = "actions修改数据"
    },
    changeStates (num:number){
        this.count += num + 2
        this.info = "actions修改数据"
    }
}

})

复制代码
//组件内使用

<template>
  <h1>{{ mainStore.count }}</h1>
  <h1>{{ mainStore.info }}</h1>
  <hr />
  <h1>{{ count }}</h1>
  <h1>{{ info }}</h1>
  
 <h1>{{ mainStore.count10 }}</h1>
 
  <p>
    <button @click="alertData">修改数据count</button>
  </p>
</template>

<script lang="ts" setup>
import { toRefs } from 'vue'
import { storeToRefs } from 'pinia'
import { useMainStore } from "../../store";
const mainStore = useMainStore();
// 解构数据,但是得到的数据是不具有响应式的,只是一次性的
// 相当于仅仅只是...mainStore而已,只是做了reactive处理,并没有做toRefs
// const { count, info } = useMainStore();
// 解决方法:
// 1. 通过使用toRefs函数,因为前面所说相当于是通过reactive处理,因此可以
// const { count, info } = toRefs(mainStore);
// 2. 通过pinia中提供的storeToRefs方法来解决,推荐使用
const { count, info } = storeToRefs(mainStore);

const alertData = () => {
  // 方式四:通过 actions 来修改数据
  mainStore.changeState()
  mainStore.changeStates(10)
}

</script>

<style>
</style>

复制代码

Guess you like

Origin juejin.im/post/7079723441674256415