第一章:vue3.0基础

1,认识vue3.0

vue3.0发布时间为2020-9-18,从项目体验上,vue3.0比起vue2.0有以下优势:

打包大小减少41%

初次渲染块55%,更新渲染块133%

内存占比少54%

2,新增内容

源码重构,新增特性,重写架构。

第二章:使用vue3.0

创建vue3.0有两种方式:使用vue-cli和使用vite

i.使用cli:

vue create vue-name 选择vue3.0项目

2,使用vite

安装vite

 npm install -g create-vite-app

创建项目

create-vite-app vue3_vite

进入项目目录后:

npm i

运行

npm run dev

第三章:从vue3.0项目中学习(cli项目)

1.配置文件

vue3.0的配置文件还是和vue2.0的一样:vue.config.js。配置内容和vue2.0的一样,也就是webpack配置。

2,main.js

import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')

就只有三段代码

createApp :是一个工厂函数

将三段代码拆开:

import { createApp } from 'vue'
import App from './App.vue'
let app=createApp(App)
app.mount('#app')

变量app是一个实例,和vm类似,但是比vm更加轻盈。

 3,app.vue

<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

在vue文件中不需要使用根标签。

目前为止。vue3.0项目上和vue2.0的不同主要集中在两个上,后面再慢慢学习。

第四章:组合式API

1,setup配置项

        vue3.0使用setup作为vue的配置项,数据、方法等都配置在setup里面,也就是说vue2.0中全部的配置都配置在setup里面。

        setup是一个函数,放回值为一个对象,该对象内容可以直接被使用

<template>
    <span>name:{{ name }}</span>
    <span>age:{{ age }}</span>
    <button @click="change">点我</button>
</template>
<script>
export default {
    name: 'App',
    setup() {
        let name = '小智';
        let age = 12;
        function change() {
            alert('点我成功');
        }
        return {
            name,
            age,
            change
        };
    }
};
</script>

注意:vue2.0和vue3.0不要混合使用,要不然vue3.0使用vue2.0的内容会包underfined。

2,ref函数实现数据响应式

ref函数会将数据封装成一个引用对象,通过这个引用对象可以实现数据的响应式。

ref对基本数据类型的处理

<template>
    <span>name:{{ name }}</span>
    <span>age:{{ age }}</span>
    <button @click="change">点我</button>
</template>
<script>
import { ref } from 'vue';
export default {
    name: 'App',
    setup() {
        let name = ref('小智');
        let age = ref(12);
        function change() {
            name.value = '你大爷';
            name.age = 190;
        }
        return {
            name,
            age,
            change
        };
    }
};
</script>

 先打印name和age变量

 这里使用了get/set方法,通过ref将数据封住成一个引用对象,在这个改变这个引用对象的value值实现数据的双向绑定。所以这里的name和age是一个引用对象。

ref对对象的处理:这里说一般对象和数组对象吧

<template>
    <span>name:{{ name }}</span
    ><br />
    <span>age:{{ age }}</span
    ><br />
    <span>学号:{{ student.no }}</span
    ><br />
    <span>班级:{{ student.class }}</span
    ><br />
    <span>数组1{{ array[0] }}</span
    ><br />
    <span>数组2:{{ array[1] }}</span
    ><br />
    <button @click="change">点我</button>
</template>
<script>
import { ref } from 'vue';
export default {
    name: 'App',
    setup() {
        let name = ref('小智');
        let age = ref(12);
        let student = ref({
            no: 12,
            class: '一班'
        });
        let array = ref(['哈哈', 'hahah']);
        console.log('基本数据类型', name);
        console.log('对象', student);
        console.log('数组', array);
        function change() {
            name.value = '你大爷';
            age.value = 190;
            student.value.no = 10000;
            student.value.class = '100班级';
            array.value[0] = 'biaiabi';
            array.value[1] = 'biaiabi';
        }
        return {
            name,
            age,
            student,
            array,
            change
        };
    }
};
</script>

打印结果:

 我们可以看到,ref将对象封装成一个proxy,而proxy则是vue3.0响应式的另一种方案——代理。

测试设置值得时候,只需要通过:

student.value.class = '100班级';
array.value[0] = 'biaiabi';

的方式设置即可,其中student.value不再是一个属性值而是一个proxy代理对象实现响应,而基本数据类型设置值只需要name.value设置值,是使用defineProperty实现响应

注意:单层的get/set放在原型对象中。对象的层次也是使用defineProperty,这里的对象单层是指其引用标识,这个引用标识相当一个基本数据类型

 

 可以得出结论,在vue3.0对响应式的处理有两个部分

基本数据类型:使用 Object.defineProperty

引用类型:使用的是window.Proxy

ref对set,map的处理

对map:

<script>
import { ref } from 'vue';
export default {
    name: 'App',
    setup() {
        let map = ref(new Map());
        map.value.set('key', '12');
        console.log('取值', map.value.get('key'));
        console.log('map', map.value);
    }
};
</script>

对set处理

<script>
import { ref } from 'vue';
export default {
    name: 'App',
    setup() {
        let set = ref(new Set());
        set.value.add('12');
        set.value.add('13');
        console.log('set', set);
        console.log('value', set.value);
        for (let item of set.value) {
            console.log('取值', item);
        }
    }
};
</script>

ref函数封装的数据,ref.value是数据的本身

3.reactive函数

我们知道的一点:对于ref来说,ref可以实现基本数据类型和引用类型的响应式。

基本数据类型:使用 Object.defineProperty的get/set实现响应式

引用数据类型:使用reactive函数实现响应式(封装window.Proxy)

vue3.0提供了一个函数来实现对象类型的响应数据——reactive

reactive函数接收一个对象,返回一个代理对象,该代理对象可实现“深层次”的代理(联想拷贝),数据有多层也可以实现响应式。

<template>
    <div>姓名:{{ student.name }}</div>
    <br />
    <div>年龄:{{ student.age }}</div>
    <br />
    <div>班级:{{ student.classNO }}</div>
    <br />
    <button @click="change">改变</button>
</template>
<script>
import { reactive } from 'vue';
export default {
    name: 'App',
    setup() {
        let student = reactive({
            name: '小智',
            age: 18,
            classNO: 123
        });
        console.log("reactive",student);
        function change() {
            student.name = '小白';
            student.age = 12;
            student.classNO = 12345;
        }
        return {
            student,
            change
        };
    }
};
</script>

 reactive函数可以处理任意引用类型,例如数组、map、set等。

4,vue3.0实现响应式原理

vue2.0响应式的弊端主要有:更新和删除属性界面不会更新,通过数据下标更新数组,界面不会更新。

在vue2.0解决上述问题,提供了许多API,例如更新$set,删除$delete等。对数组则是封装了数组的方法。

vue3.0实现响应式的原理是封装了window.Proxy实现数据响应式,对数据进行更新删除操作没有这个问题。

实现响应式原理:

<script>
  let person={
     name:'小智',
     age:18,
  }
let p=new Proxy(person,{})
</script>

这个就可以实现数据增删改查的响应情况,如果需要响应到视图,需要在第二个参数进行相关处理捕获到更改信息并响应到视图上。

<script>
        let person = {
            name: '小智',
            age: 18,
        }
 
               
                
        let p = new Proxy(person, {
            get(target, propName) {
                console.log("读取了", target, propName, target[propName]);
                //return target[propName]
                return  Reflect.get(target,propName)
            },
            set(target, propName, value) {
                //target[propName]=value
                Reflect.set(target,propName,value)
            },
            deleteProperty(target, propName) {
                console.log("删除", target, propName,target[propName]);
                //delete target[propName]
             return Reflect.defineProperty(target,propName)
                
            }
        })
</script>

 get(target, propName):target原对象,propName读取的属性名

set(target, propName, value):target原对象,propName修改的属性名,value修改成的属性值,target[propName]原属性值

deleteProperty(target, propName):target原对象,propName删除的属性名,

vue3.0使用反射对数据进行增删改,(很多Object的方法都逐渐移到Reflect上,主要是如果同名的时候,反射会有个返回值告诉是否操作成功)

只要掌握vue2.0的响应式原理,vue3.0的更好理解。

注意:数据是可以追加属性的,也是响应式的

5,setup的参数(setup两个注意点)

i,setup会在beforeCreate之前调用一次,此时this为underfined

ii.setup参数:

setup(props,context)

props:值为一个对象,包含外部传入得props且组件内部声明接收了属性。组件使用props接收后,该参数包含接收到的属性

context:上下文对象,包括了

attrs:相当this.$attrs

slots:相当this.$slots

emit:相当this.$emit

a,props

App.vue

<template>
    <Dome :name="student.name" @change="change" />
</template>
<script>
import { reactive } from 'vue';
import Dome from './components/dome.vue';
export default {
    name: 'App',
    components: {
        Dome
    },
    setup() {
        let student = reactive({
            name: '小智'
        });   
        return {
            student
        };
    }
};
</script>
<style scoped></style>
<template>
    <div>{{ data.name }}</div>
</template>
<script>
import { reactive } from 'vue';
export default {
    name: 'dome',
    props: ['name'],
    emits: ['change'],
    components: {},
    setup(props) {
        let data = reactive({
            name: props.name
        });
        console.log(props);
        return {
            data
        };
    }
};
</script>
<style scoped></style>

我们可以看到dome使用props接收属性之后,setup的prop参数含有代理的属性。

b,context:上下文对象

在父组件中:

<Dome :name="student.name" :age="student.age" @change="change">

子组件中:

<template>
    <div>{{ data.name }}</div>
    <slot></slot>
    <button @click="test">點我</button>
</template>
<script>
import { reactive } from 'vue';
export default {
    name: 'dome',
    props: ['name'],
    emits: ['change'],
    components: {},
    setup(props, context) {
        let data = reactive({
            name: props.name
        });
        console.log('props', props);
        console.log('emit', context.emit);
        console.log('attrs', context.attrs);
        console.log('slots', context.slots);
        function test() {
            context.emit('change', '大白猫');
        }
        return {
            data,
            test
        };
    }
};
</script>
<style scoped></style>

 标注的部分注意:如果子组件中没有使用props来接收属性,这个属性就会放到attrs中,接收了就不放进去。

警告:如果向字组件传入一个方法,需要使用一下定义:

 emits: ['change'],

要不然vue3.0会在控制台给出警告,特别烦人碍眼!!!

6,计算属性computed

用法:和ref或reactive类似

简写:

<template>
    <div>{{ data.num1 }}+{{ data.num2 }}={{ num }}</div>
    <button @click="change1">加法</button>
    <button @click="change2">减法</button>
</template>
<script>
import { reactive, computed } from 'vue';
export default {
    name: 'App',
    setup() {
        let data = reactive({
            num1: 12,
            num2: 1
        });
        let num = computed(() => {
            return data.num1 + data.num2;
        });
        function change1() {
            data.num1 = data.num1 + 1;
        }
        function change2() {
            data.num2 = data.num2 + 100;
        }
        return {
            num,
            data,
            change1,
            change2
        };
    }
};
</script>
<style scoped></style>

get/set方式写法

let num = computed({
    get() {
       console.log('调用了');
       return data.num1 + data.num2;
    },
    set(newValue, oldValue) {
       console.log("修改调用");
    }
});

7,监视watch

a,监视ref单个值

<template>
    <div>{{ mun }}+{{ age }}}</div>
    <br />
    <button @click="change1">加法</button><br />
    <button @click="change2">减法</button>
</template>
<script>
import { ref, watch } from 'vue';
export default {
    name: 'App',
    setup() {
        let mun = ref('xiaozhi');
        let age = ref(12);
        watch(mun, (oldValue, newValue) => {
            console.log('改变了', oldValue, newValue);
        });
        watch(age, (oldValue, newValue) => {
            console.log('改变了', oldValue, newValue);
        });
        function change1() {
            mun.value = 'hahah';
        }
        function change2() {
            age.value = 233;
        }
        return {
            mun,
            age,
            change1,
            change2
        };
    }
};
</script>

 b,监视多个ref值

<template>
    <div>{{ mun }}+{{ age }}</div>
    <br />
    <button @click="change1">加法</button><br />
    <button @click="change2">减法</button>
</template>
<script>
import { ref, watch } from 'vue';
export default {
    name: 'App',
    setup() {
        let mun = ref('xiaozhi');
        let age = ref(12);
        watch([age, mun], (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        });
        function change1() {
            mun.value = 'hahah';
        }
        function change2() {
            age.value = 233;
        }
        return {
            mun,
            age,
            change1,
            change2
        };
    }
};
</script>
<style scoped></style>

 监视多个值的时候,oldValue和newValue是一个数组,

监视单个值不能用.value.

监视ref值(值为对象)

  watch(data.value, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        });
或者
  watch(data, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        },{deep:true});

c.监视reactive

<template>
    <div>{{ data.name }}:{{ data.age }}</div>
    <br />
    <button @click="change1">加法</button><br />
    <button @click="change2">减法</button>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 12
        });
        watch(data, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        });
        function change1() {
            data.name = 'hahah';
        }
        function change2() {
            data.age = 233;
        }
        return {
            data,
            change1,
            change2
        };
    }
};
</script>

 监视一个reactive数据的时候(不包括其属性),无法获取到oldValue(现价段无法获取reactive数据的oldValue),解决方式,将其需要获取改变状态的数据提出来单独监视。

例如我们需要监视age值得时候,建age提出来单独使用ref监视

  let age=ref(12) 

d.监视多个reactive

<template>
    <div>{{ data.name }}:{{ data.age }}___{{ data1.name }}+{{ data1.age }}</div>
    <br />
    <button @click="change1">加法</button><br />
    <button @click="change2">减法</button>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 12
        });
        let data1 = reactive({
            name: 'wowowo',
            age: 134
        });
        watch([data, data1], (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        });
        function change1() {
            data.name = 'hahah';
        }
        function change2() {
            data.age = 233;
        }
        return {
            data,
            data1,
            change1,
            change2
        };
    }
};
</script>

 监视两个的时候和ref一样,oldValue和newValue是一个数组,

注意:监视reactive数据的时候,默认开启深度监视,而且无法关闭

 watch(data, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        },{deep:false});
不生效

监视reactive数据的属性(对象或基本类型)的时候,深度监视默认打开,而且可以关闭。

 watch(data.job, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        },{deep:false});
deep生效

e.监视reactive数据的属性(基本数据类型的时候),写成函数并返回。

<template>
    <div>{{ data.name }}:{{ data.age }}</div>
    <br />
    <button @click="change1">加法</button><br />
    <button @click="change2">减法</button>
</template>
<script>
import { reactive, watch } from 'vue';
export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 12,
            job: {
                zhiye: '学生',
                zhize: '学习'
            }
        });

        watch(
            () => {
                return data.name;
            },
            (newValue, oldValue) => {
                console.log('改变了', newValue, oldValue);
            },
            { deep: true }
        );
        function change1() {
            data.name = 'hahah';
        }
        function change2() {
            data.age = 233;
        }
        return {
            data,
            change1,
            change2
        };
    }
};
</script>

 f,监视reactive的属性为对象的方式和与函数的方式

let data = reactive({
            name: 'xiaozhi',
            age: 12,
            job: {
                j1: {
                    zhiye: '学生',
                    zhize: '学习'
                }
            }
        });

为对象:

watch(data.job, (newValue, oldValue) => {
            console.log('改变了', newValue, oldValue);
        });

为函数:

   watch(
            () => data.job,
            (newValue, oldValue) => {
                console.log('改变了', newValue, oldValue);
            }
        );

 

 无法监视到其变化,加入深度监视之后:

       watch(
            () => data.job,
            (newValue, oldValue) => {
                console.log('改变了', newValue, oldValue);
            },
            { deep: true }
        );

 因此:监视对象的时候,如果之间监视reactive数据中的对象,默认开启深度监视而且无法关闭,监视reactive数据中的对象以函数的形式必须开启深度监视才能监视到下一层的数据。

8,watchEffect:监视

用法:

watchEffect(()=>{}):无参数,返回值,用于在回调函数内部使用到的数据,初始化调用一次

<template>
    <div>{{ data.name }}:{{ data.age }}:{{ data.job.j1.zhiye }}</div>
    <br />
    <button @click="change1">姓名</button><br />
    <button @click="change2">职业</button>
</template>
<script>
import { reactive, watch, watchEffect } from 'vue';
export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 12,
            job: {
                j1: {
                    zhiye: '学生',
                    zhize: '学习'
                }
            }
        });
        watchEffect(() => {
            let x1 = data.name;
            let x2 = data.job;
            console.log('监视', x1, x2);
        });
        function change1() {
            data.name = '小虎';
        }
        function change2() {
            data.job.j1.zhiye = '老师';
        }
        return {
            data,
            change1,
            change2
        };
    }
};
</script>
<style scoped></style>

初始化调用一次:

点击姓名:

 

点击职业:

 可以看到必须要使用data中的数据才能触发该监视函数

再换一种写法:修改x2

 let x2 = data.job.j1.zhiye;

点击职业:

9.vue3的生命周期

五.组合api2

1.自定义hook

hooks和mixin有点像,都是为了复用代码,只是hooks是一系列函数并放回相关数据。

自定义一个js文件hooks.js

import { reactive, onMounted } from 'vue';
const getPoint = function() {
    let data = reactive({
        pageX: 0,
        pageY: 0
    });
    onMounted(() => {
        window.addEventListener('click', function(event) {
            data.pageX = event.pageX;
            data.pageY = event.pageY;
        });
    });
    return data;
};
export { getPoint };

导入hooks

<template>
    <div>{{ data.pageX }}:{{ data.pageY }}</div>
    <br />
    <button @click="change1">姓名</button><br />
    <button @click="change2">职业</button>
</template>
<script>
import { getPoint } from './hooks/usePoint';
export default {
    name: 'App',
    setup() {
        let data = getPoint();
        return { data };
    }
};
</script>
<style scoped></style>

hooks规范

hooks是一个函数,函数如果接受参数必须接受响应式数据,返回值也必须是响应式数据,函数内部可以定义多个函数变量用于实现多个操作功能。

2.toRef和toRefs

用于创建一个ref对象(必须)使其value指向另一个ref的属性

 作用:

创建一个toRef对象(实质是一个ref对象)的value指向另一个ref对象的属性。

将响应式对象的某个属性向外提供使用。

<template>
    <div>{{ name }}:{{ data.age }}:{{ pq }}</div>
    <br />
    <button @click="change2">职业</button>
</template>
<script>
import { reactive, toRef } from '@vue/reactivity';

export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 23,
            job: {
                p: {
                    zhiye: '打工人'
                }
            }
        });
        function change2() {
            data.name = 'xiaobai';
            data.age = 34;
            data.job.p.zhiye = 'hh';
        }
        return {
            data,
            name: toRef(data, 'name'),
            pq: toRef(data.job.p, 'zhiye'),
            change2
        };
    }
};
</script>
<style scoped></style>

 toRefs:批量处理

<template>
    <div>{{ name }}:{{ data.age }}:{{ job.p.zhiye }}</div>
    <br />
    <button @click="change2">职业</button>
</template>
<script>
import { reactive, toRefs } from '@vue/reactivity';

export default {
    name: 'App',
    setup() {
        let data = reactive({
            name: 'xiaozhi',
            age: 23,
            job: {
                p: {
                    zhiye: '打工人'
                }
            }
        });
        function change2() {
            data.name = 'xiaobai';
            data.age = 34;
            data.job.p.zhiye = 'hh';
        }
        return {
            data,
            ...toRefs(data),
            change2
        };
    }
};
</script>
<style scoped></style>

 

 批量处理只能处理一层数据,深层的单独取出,如上html代码。或者如下

 ...toRefs(data.job.p),

六,其他组合API

1,shallowReactive和shallowRef

shallowReactive:只考虑第一层的数据响应

shallowRef:只考虑基本数据类型的响应式不考虑引用类型的响应

2,readonly和shallowReadonly

readonly:数据只读(深只读)

let data = reactive({
     name: 'xiaozhi',
     age: 23,
    });
data=readonly(data)

shallowReadonly:数据只读(浅只读,一层)

3,toRow和markRaw

toRow:将一个响应式数据传成普通对象(ref数据不可用)。

markRaw:标记一个数据,使其永远不会变成响应式数据。

比如第三方数据,如果不使其冻结,添加到另一个对象的时候会注意查询其属性使其变成响应式,这回导致效率问题。

let data=reative{...}
let code={....}
data.code=markRow(code)

4,自定义ref:customRef

参数:

track:通知vue追踪读取的数据

trigger:通知vue重新渲染模板

 setup() {
        let data = myRef('xx');
        function myRef(value) {
            return customRef((track, trigger) => {
                return {
                    get() {
                        track(); //通知vue追踪读取的数据
                        return value;
                    },
                    set(newValue, oldValue) {
                        console.log(newValue, oldValue);
                        value = newValue;
                        trigger(); //通知vue重新渲染模板
                    }
                };
            });
        }
        return {
            data
        };
    }

set方法没有oldValue。

5,provide和inject

用于祖先后代元素之间的通信

祖先

  setup(props, context) {
        let data = reactive({
            name: '给噎死',
            age: 100
        });
        provide('parentData', data);
        return {
            ...toRefs(data)
        };
    }

后代:

   setup(props, context) {
        let parentData = inject('parentData');
        let data = reactive(parentData);
        return {
            ...toRefs(data)
        };
    }

 由provide向后代组件传数据,后代使用inject接收数据

注意:祖先元素的数据和后代的数据是具有双向相应式的,同时修改一方的数据,另一方的数据也会跟着变。

7,响应数据类型的判断

七.vue3.0组件

1.fragment组件

vue3.0模板中无需一个根标签的原因是vue3.0自动添加添加一个虚拟组件fragment作为模板的更标签。

2.teleport组件

作用:将包含的内容标签移动到特定html标签内。

 例如我想将ul标签全部移动到body内和div标签同级,就可以使用eleport组件

用法:

 <teleport to="body">......</teleport>

例如设计一个弹框就可以使用这个组件。

app.vue

<div class="app">
        <h1>祖先</h1>
        <son />
    </div>

son.vue

<div class="son">
        <h2>后代组件</h2>
        <child />
    </div>

child.vue

<template>
    <div class="child">
        <h4>后代元素</h4>
        <button @click="show = true">打开</button>
        <teleport to="body">
            <div class="shallow" v-if="show">
                <div class="glob">
                    <p>这是一个弹窗</p>
                    <button @click="show = false">关闭</button>
                </div>
            </div>
        </teleport>
    </div>
</template>
<script>
import { reactive, inject, toRefs } from 'vue';
export default {
    name: 'child',
    props: [],
    components: {},
    setup(props, context) {
        let data = reactive({
            show: false
        });
        return {
            ...toRefs(data)
        };
    }
};
</script>
<style scoped>
.child {
    width: 200px;
    background-color: yellowgreen;
}
.glob {
    height: 300px;
    width: 300px;
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: violet;
}
.shallow {
    position: absolute;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    background-color: lightslategrey;
}
</style>

在child.vue里面设计弹框,将弹框内容移动到body里面去。

3.Suspense组件

异步组件,官方还在试验阶段。。。。

八.vue3.0其他改动内容

截图了,都是比较好理解的内容。

 

 

 来源:尚硅谷 

 终于把vue3.0学完了,下一步进军typescript!!!!

Logo

前往低代码交流专区

更多推荐