前端菜狗Vue3学习笔记

519 阅读3分钟

开篇

Vue3与Vue2对比语法是最大的区别就是新增的组合式 API,使用组合式API可以大大的提高代码的可读性、可维护性

回顾一下我们使用Vue2语法去修改一个功能时候,首先通过路由定位到对应的视图(.vue文件),再通过模板(templete)定位到相应模板元素,再去methods中找到元素上的绑定方法,方法中又涉及到data()中的数据,data()中的数据可能来源自props也可能来自生命周期函数中的API调用,若是一个纯展示的模块,可能还涉及到computed(计算属性)。若这个组件中涉及多个业务逻辑,那么整个文件中的代码是相当冗长的,也就造成了官方提到的“关注点分离”问题,而使用组合式API就是为了解决这个痛点。(个人理解、轻喷)

Vue3新特性

1.Composition API

组合式API中涉及的知识点较多,接下来我们一一展示,首先用Vue-CLI创建一个vue3项目(不是本文重点,自行查阅官方文档)。

1.1 setup()、ref

<template>
    <div>
        <ul>
            <li v-for="(item,index) in menu" :key="item+index" @click="choose(index)">
                {{`${index}---${item}`}}
            </li>
        </ul>
        <h2>{{chooseFood}}</h2>
    </div>
</template>

<script>
import { ref } from 'vue'
    export default {
        name:'test',
        setup() {
            const menu = ref(['红烧茄子','油焖茄子','干瘪茄子'])
            console.log('menu----',menu);
            console.log('created');
            const chooseFood = ref('')
            const choose = index =>{
                chooseFood.value = menu.value[index]
                console.log(menu.value[index]);
            }
            return{
               menu,
               chooseFood,
               choose
            }
        }
    }
</script>
//setup()函数,组合式API存放的位置,效果上相当于beforecreate+created,也就是说我们可以在这里动态的获取数据,变相的取代了data、methods但需要注意声明顺序;
//使用ref函数声明响应式数据,需要在视图中使用的数据、方法通过return返回;

image.png

1.2 reactive

<template>
    <div>
        <ul>
            <li v-for="(item,index) in data.menu" :key="item+index" @click="data.choose(index)">
                {{`${index}---${item}`}}
            </li>
        </ul>
        <h2>{{data.chooseFood}}</h2>
    </div>
</template>

<script>
import { ref,reactive } from 'vue'
    export default {
        name:'test',
        setup() {
            const data = reactive({
                menu : ['红烧茄子','油焖茄子','干瘪茄子'],
                chooseFood:'',
                choose : index =>{
                    data.chooseFood = data.menu[index]
                    console.log(data.menu[index]);
                }
            })
            console.log('menu----',data.menu);
            console.log('created');
            return{
               data
            }
        }
    }
</script>
//通过reactive将数据、方法代理在data对象中,达到的效果跟1.1一致。

image.png

1.3 toRefs

<template>
    <div>
        <ul>
            <li v-for="(item,index) in menu" :key="item+index" @click="choose(index)">
                {{`${index}---${item}`}}
            </li>
        </ul>
        <h2>{{chooseFood}}</h2>
    </div>
</template>

<script>
import { ref,reactive,toRefs } from 'vue'
    export default {
        name:'test',
        setup() {
            const data = reactive({
                menu : ['红烧茄子','油焖茄子','干瘪茄子'],
                chooseFood:'',
                choose : index =>{
                    data.chooseFood = data.menu[index]
                    console.log(data.menu[index]);
                }
            })
            const refData = toRefs(data)
            console.log('refData---',refData);
            return{
               ...refData
            }
        }
    }
</script>
//若直接return {...data}则数据不是响应式;
//通过toRefs包装后,return{...refData}等价于return{menu: Ref<Object>,chooseFood:Ref<String>,choose: Ref<function>
}

toRefs:将响应式对象转换为普通对象,其中结果对象的每个 property 都是指向原始对象相应 property 的 ref

image.png

1.4 生命周期函数

Vue2--------------vue3              Vue3新增:
beforeCreate  -> setup()            onRenderTracked(跟踪每一个值)
created       -> setup()            onRenderTriggered(跟踪变化值)
beforeMount   -> onBeforeMount
mounted       -> onMounted
beforeUpdate  -> onBeforeUpdate
updated       -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed     -> onUnmounted
activated     -> onActivated
deactivated   -> onDeactivated
errorCaptured -> onErrorCaptured

首先是新增的setup函数,然后是名称上的变化,Vue2中的生命周期函数还是可以照常使用的(但建议还是使用新的hooks),然后是新增的两个用于跟踪数据状态,简化调试的钩子(onRenderTracked、onRenderTriggered),setup中使用其他生命周期函数需要先引入;

<template>
    <div>
        <ul>
            <li v-for="(item,index) in menu" :key="item+index" @click="choose(index)">{{`${index}---${item}`}}</li>
        </ul>
        <h2>{{chooseFood}}</h2>
    </div>
</template>

<script>
import { ref,reactive,toRefs,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onRenderTracked,onRenderTriggered } from 'vue'
    export default {
        name:'test',
        setup() {
            const data = reactive({
                menu : ['红烧茄子','油焖茄子','干瘪茄子'],
                chooseFood:'',
                choose : index =>{
                    data.chooseFood = data.menu[index]
                    console.log(data.menu[index]);
                }
            })
            const refData = toRefs(data)
            console.log('1-开始创建组件-----setup()',);
            onBeforeMount(() => {
                console.log("2-组件挂载到页面之前执行-----onBeforeMount()");
            })

            onMounted(() => {
                console.log("3-组件挂载到页面之后执行-----onMounted()");
            })
            onBeforeUpdate(() => {
                console.log("4-组件更新之前-----onBeforeUpdate()");
            })

            onUpdated(() => {
                console.log("5-组件更新之后-----onUpdated()");
            })
            // onRenderTracked((event)=>{
            //     console.log("onRenderTracked--------------->");
            //     console.log(event);
            // }),
            onRenderTriggered((event)=>{
                console.log("onRenderTriggered--------------->");
                console.log(event);
            })

             return{
               ...refData
            }
        }
    }
</script>

image.png

1.5 watch

它接受 3 个参数:

  • 一个想要侦听的响应式引用getter 函数
  • 一个回调
  • 可选的配置选项

与Vue2中watch对比,首先针对多个数据监听时,用数组的形式而不是写多个watch;然后监听使用reactive函数声明的变量时,使用getter函数的形式,个人理解就是相当于Vue中监听复杂类型时,开启deep:true深度监听一个性质。

<template>
    <div>
        <ul>
            <li v-for="(item,index) in menu" :key="item+index" @click="choose(index)">
              {{`${index}---${item}`}}
            </li>
        </ul>
        <h2>{{chooseFood}}</h2>
        <div>
            <h3>{{name}}</h3>
            <button @click="rename">rename</button>
        </div>
    </div>
</template>

<script>
import { ref,reactive,toRefs, watch } from 'vue'
    export default {
        name:'test',
        setup() {
            const name = ref('luo')
            const rename = ()=>{
                name.value = 'joker'
            }
            const data = reactive({
                menu : ['红烧茄子','油焖茄子','干瘪茄子'],
                chooseFood:'',
                choose : index =>{
                    data.chooseFood = data.menu[index]
                    console.log(data.menu[index]);
                }
            })
            console.log(name);
            console.log(data);
            watch([()=>data.chooseFood,name],(newValue,oldValue)=>{
                console.log(`new--->${newValue}`);
                console.log(`old--->${oldValue}`);    
            })            
            const refData = toRefs(data)
             return{
               ...refData,
               name,
               rename
            }
        }
    }
</script>

image.png

1.6 computed

<template>
    <div>
        <ul>
            <li v-for="(item,index) in menu" :key="item+index" @click="choose(index)">
               {{`${index}---${item}`}}
            </li>
        </ul>
        <h2>{{chooseFood}}</h2>
        <div>
            <h3>{{name}}</h3>
            <h3>{{`doubleName:${doubleName}`}}</h3>
            <button @click="rename">rename</button>
        </div>
    </div>
</template>

<script>
import { ref,reactive,toRefs,computed,watch } from 'vue'
    export default {
        name:'test',
        setup() {
            const name = ref('luo')
            const rename = ()=>{
                name.value = 'joker'
            }
            const data = reactive({
                menu : ['红烧茄子','油焖茄子','干瘪茄子'],
                chooseFood:'',
                choose : index =>{
                    data.chooseFood = data.menu[index]
                    console.log(data.menu[index]);
                }
            })
            watch([()=>data.chooseFood,name],(newValue,oldValue)=>{
                console.log(`new--->${newValue}`);
                console.log(`old--->${oldValue}`);    
            })         
            const doubleName = computed(()=>{return name.value+name.value})   
            const refData = toRefs(data)
             return{
               ...refData,
               name,
               rename,
               doubleName
            }
        }
    }
</script>

image.png

1.7 Teleport组件

在Vue2中所有的组件都是挂载在#app节点下的,为避免一些样式侵入需要做一些复杂的嵌套,而Vue3的Teleport组件可以让我们将组件挂载到任意节点中去。

//Modal.vue
<template>
        <div id="center">
            <h2>test demo</h2>  
        </div>
</template>

<script>
export default {}
</script>

<style scoped>
    #center {
        width: 200px;
        height: 200px;
        border: 2px solid black;
        background: white;
        display: flex;
        justify-content: center;
        align-items: center;
        }
</style>

//App.vue
<template>
      <Modal />
</template>

<script lang="ts">
import Modal from '../src/components/Modal.vue'
const app = {
  name: "App",
  components: {
    Modal
  }
};
export default app;
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

image.png

使用teleport组件
//Modal.vue
<template>
    <teleport to='#modal'>
        <div id="center">
            <h2>test demo</h2>  
        </div>
    </teleport>
</template>

image.png

1.8 Suspense组件

//AsyncShow.vue
<template>
    <div>
        <h1>{{ result }}</h1>
    </div>
</template>

<script>
export default ({
    setup() {
        return new Promise((resolve,reject)=>{
            setTimeout(() => {
                return resolve({result: 'check suspense'})
            }, 3000);
        })
    }
})
</script>
 
 //App.vue
 <template>
      <Suspense>
    <template #default>
      <AsyncShow />
    </template>
    <template #fallback>
      <div>
         Loading....
      </div>
    </template>
  </Suspense>
</template>

2021-09-20-15-40-57.gif

用setup()做mixin的工作

在Vue2中我们使用mixin复用一些通用逻辑代码块,像Pagination分页等;确实提高了开发效率,但是在具体的业务页,很难分清属性是来自哪里;

    //timeHooks.js
    import {ref} from "vue"
    const nowtime = ref("00:00:00");
    const getNowTime = () => {
      const now = new Date();
      const hour = now.getHours() < 10? "0" + now.getHours() : now.getHours();
      const minutes = now.getMinutes() < 10? "0" + now.getMinutes() : now.getMinutes();
      const seconds = now.getSeconds() < 10? "0" + now.getSeconds() : now.getSeconds();
      nowtime.value =  hour + ":" + minutes + ":" +seconds
      setTimeout(getNowTime,1000);  
    };

    export {nowtime , getNowTime}
    
    //App.vue
    </template>
     <div>
        {{nowtime}}
     </div>
    </template>
    <script>
    import {getNowTime,nowtime} from '../src/hooks/timeHooks.js'
    const app = { 
      name: "App",
      setup() {
        getNowTime()
        return {
          nowtime
        };
      },
    };
    export default app;
    </script>
    

更加的直观。

以上内容为自身结合vue3官方文档与胖老师的教程的学习笔记,欢迎指正。

v3.cn.vuejs.org/guide/intro…

jspang.com/detailed?id…