vue-cli v4.5/:webpack
vite:Vite | 下一代的前端工具链
vite为何启动快?
开发环境使用ES6 Module,可以无需预先打包,而是采用实时编译的方式;
但是生产环境使用rollop,并不会快很多。
webpack需要转es5。
- import { createApp } from 'vue'
- import App from './App.vue'
-
- const app = createApp(App)
- app.mount('#app')
vue-router:Vue Router | Vue.js 的官方路由
vuex:Vuex 是什么? | Vuex
this 为 undefined,可通过 getCurrentInstance() 获取组件实例。
- export default {
- name:'App',
- components:{Demo},
- props:['msg','school'],//props如果不声明接收,值会在$attrs(vue2)
- emits:['hello'],//子组件向父组件传值要声明emit(vue2)
- //在beforeCreate之前执行一次,this是undefined
- setup(props,context){//context为上下文对象,包含attrs/slots/emit(同vue2)
- //数据
- let name = '张三'
- let age = 18
-
- //方法
- function sayHello(){
- alert(`我叫${name},我${age}岁了,你好啊!`)
- }
- //返回一个对象(常用)
- return {
- name,
- age,
- sayHello,
- }
- }
- }
实现原理:
通过Proxy(代理): 拦截对象中任意属性的变化, 包括:属性值的读写、属性的添加、属性的删除等。
通过Reflect(反射): 对源对象的属性进行操作。
- //proxy增删改属性都能获取到;Object移植到Reflect:
- //#region
- const p = new Proxy(person,{
- //有人读取p的某个属性时调用
- get(target,propName){
- console.log(`有人读取了p身上的${propName}属性`)
- //return target[propName]
- return Reflect.get(target,propName)
- },
- //有人修改p的某个属性、或给p追加某个属性时调用
- set(target,propName,value){
- console.log(`有人修改了p身上的${propName}属性,我要去更新界面了!`)
- //target[propName] = value
- Reflect.set(target,propName,value)
- },
- //有人删除p的某个属性时调用
- deleteProperty(target,propName){
- console.log(`有人删除了p身上的${propName}属性,我要去更新界面了!`)
- //return delete target[propName]
- return Reflect.deleteProperty(target,propName)
- }
- })
- //#endregion
ref函数
ref函数用来定义一个响应式的数据。(基本数据)通过.value修改值
基本类型的数据:响应式依然是靠Object.defineProperty()的get与set完成的。
对象类型的数据:内部求助了Vue3.0中的一个新函数—— reactive函数。
- <h1>一个人的信息h1>
- <h2>姓名:{{name}}h2>
- <h2>年龄:{{age}}h2>
- <h3>工作种类:{{job.type}}h3>
- <h3>工作薪水:{{job.salary}}h3>
- <button @click="changeInfo">修改人的信息button>
-
- <script>
- import {ref} from 'vue'
- export default {
- name: 'App',
- setup(){
- //数据
- let name = ref('张三')//基本类型
- let age = ref(18)
- let job = ref({//对象类型
- type:'前端工程师',
- salary:'30K'
- })
-
- //方法
- function changeInfo(){
- name.value = '李四'
- age.value = 48
- //ref函数返回一个RefImpl对象(引用实现的实例),实现响应式:__proto__上有setter和getter
- console.log(name,age)
- job.value.type = 'UI设计师'
- job.value.salary = '60K'
- //job.value是一个Proxy对象,封装在reactive函数中
- console.log(job.value)
-
- }
-
- //返回一个对象(常用)
- return {
- name,
- age,
- job,
- changeInfo
- }
- }
- }
- script>
-
why .value
vue3用了proxy实现响应式,那如果用reactive定义引用类型的话是正常的,如果用ref是一个基本类型的值,就没办法实现proxy的拦截,所以vue3中对ref定义的值进行了包装,变成一个对象实现proxy进行拦截达到响应式。
reactive函数
reactive函数定义一个对象类型的响应式数据(深层次的)。
接收一个对象或数组,返回一个proxy对象。
内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作。
- <template>
- <h1>一个人的信息h1>
- <h2>姓名:{{person.name}}h2>
- <h2>年龄:{{person.age}}h2>
- <h3>工作种类:{{person.job.type}}h3>
- <h3>工作薪水:{{person.job.salary}}h3>
- <h3>爱好:{{person.hobby}}h3>
- <h3>测试的数据c:{{person.job.a.b.c}}h3>
- <button @click="changeInfo">修改人的信息button>
- template>
-
- <script>
- import {reactive} from 'vue'
- export default {
- name: 'App',
- setup(){
- //数据
- let person = reactive({
- name:'张三',
- age:18,
- job:{
- type:'前端工程师',
- salary:'30K',
- a:{
- b:{
- c:666
- }
- }
- },
- hobby:['抽烟','喝酒','烫头']
- })
-
- //方法
- function changeInfo(){
- person.name = '李四'
- person.age = 48
- person.job.type = 'UI设计师'//job直接就是proxy对象
- person.job.salary = '60K'
- person.job.a.b.c = 999
- person.hobby[0] = '学习'
- }
-
- //返回一个对象(常用)
- return {
- person,
- changeInfo
- }
- }
- }
- script>
-
toRef:创建一个 ref 对象,返回ObjectRefImpl对象,用于将响应式对象中的某个属性单独提供给外部使用。两者保持引用关系
toRefs:批量创建多个 ref 对象。
- <template>
- <h4>{{person}}h4>
- <h2>姓名:{{name}}h2>
- <h2>年龄:{{age}}h2>
- <h2>薪资:{{job.j1.salary}}Kh2>
- <button @click="name+='~'">修改姓名button>
- <button @click="age++">增长年龄button>
- <button @click="job.j1.salary++">涨薪button>
- template>
-
- <script>
- import {ref,reactive,toRef,toRefs} from 'vue'
- export default {
- name: 'Demo',
- setup(){
- //数据
- let person = reactive({
- name:'张三',
- age:18,
- job:{
- j1:{
- salary:20
- }
- }
- })
-
- // const name1 = person.name//无响应式
- // console.log('%%%',name1)
-
- // const name2 = toRef(person,'name')//有响应式
- // console.log('####',name2)
-
- const x = toRefs(person)//只交出第一层
- console.log('******',x)
-
- const ageRef = toRef(person, 'age')
- setTimeout(() => {
- person.age = 25
- }, 1500)
-
- setTimeout(() => {
- ageRef.value = 30 // .value 修改值
- }, 3000)
-
- //返回一个对象(常用)
- return {
- person,
- // name:toRef(person,'name'),
- // age:toRef(person,'age'),
- // salary:toRef(person.job.j1,'salary'),
- ...toRefs(person)
- }
- }
- }
- script>
-
- <template>
- <h1>一个人的信息h1>
- 姓:<input type="text" v-model="person.firstName">
- <br>
- 名:<input type="text" v-model="person.lastName">
- <br>
- <span>全名:{{person.fullName}}span>
- <br>
- 全名:<input type="text" v-model="person.fullName">
- template>
-
- <script>
- import {reactive,computed} from 'vue'
- export default {
- name: 'Demo',
- setup(){
- //数据
- let person = reactive({
- firstName:'张',
- lastName:'三'
- })
- //计算属性——简写(没有考虑计算属性被修改的情况)
- /* person.fullName = computed(()=>{
- return person.firstName + '-' + person.lastName
- }) */
-
- //计算属性——完整写法(考虑读和写)
- person.fullName = computed({
- get(){
- return person.firstName + '-' + person.lastName
- },
- set(value){
- const nameArr = value.split('-')
- person.firstName = nameArr[0]
- person.lastName = nameArr[1]
- }
- })
-
- //返回一个对象(常用)
- return {
- person
- }
- }
- }
- script>
-
age1具有响应式
watch
监视reactive定义的响应式数据时:oldValue无法正确获取、强制开启了深度监视(deep配置失效)。
监视reactive定义的响应式数据中某个属性时:deep配置有效。
- <template>
- <h2>当前求和为:{{sum}}h2>
- <button @click="sum++">点我+1button>
- <hr>
- <h2>当前的信息为:{{msg}}h2>
- <button @click="msg+='!'">修改信息button>
- <hr>
- <h2>姓名:{{person.name}}h2>
- <h2>年龄:{{person.age}}h2>
- <h2>薪资:{{person.job.j1.salary}}Kh2>
- <button @click="person.name+='~'">修改姓名button>
- <button @click="person.age++">增长年龄button>
- <button @click="person.job.j1.salary++">涨薪button>
- template>
-
- <script>
- import {ref,reactive,watch} from 'vue'
- export default {
- name: 'Demo',
- setup(){
- //数据
- let sum = ref(0)
- let msg = ref('你好啊')
- let person = reactive({
- name:'张三',
- age:18,
- job:{
- j1:{
- salary:20
- }
- }
- })
- let person2 = ref({//ref定义
- name:'张三',
- age:18,
- job:{
- j1:{
- salary:20
- }
- }
- })
-
- //情况一:监视ref所定义的一个响应式数据(一个)
- watch(sum,(newValue,oldValue)=>{
- console.log('sum变了',newValue,oldValue)
- },{immediate:true})
-
- //情况二:监视ref所定义的多个响应式数据(多个)
- watch([sum,msg],(newValue,oldValue)=>{
- console.log('sum或msg变了',newValue,oldValue)
- },{immediate:true})
-
- /*
- 情况三:监视reactive所定义的一个响应式数据的全部属性(全部属性)
- 1.注意:此处无法正确的获取oldValue
- 2.注意:强制开启了深度监视(deep配置无效)
- */
- watch(person,(newValue,oldValue)=>{
- console.log('person变化了',newValue,oldValue)
- },{deep:false}) //此处的deep配置无效
- watch(person2,(newValue,oldValue)=>{
- console.log('person2的值变化了',newValue,oldValue)
- },{deep:true})
-
- //情况四:监视reactive所定义的一个响应式数据中的某个属性(某个属性)
- watch(()=>person.name,(newValue,oldValue)=>{
- console.log('person的name变化了',newValue,oldValue)
- })
-
- //情况五:监视reactive所定义的一个响应式数据中的某些属性(某些属性)
- watch([()=>person.name,()=>person.age],(newValue,oldValue)=>{
- console.log('person的name或age变化了',newValue,oldValue)
- })
-
- //特殊情况
- watch(()=>person.job,(newValue,oldValue)=>{
- console.log('person的job变化了',newValue,oldValue)
- },{deep:true}) //此处由于监视的是reactive素定义的对象中的某个属性,所以deep配置有效
-
-
- //返回一个对象(常用)
- return {
- sum,
- msg,
- person
- }
- }
- }
- script>
-
watchEffect
watch:既要指明监视的属性,也要指明监视的回调。
watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。初始化时执行一次,用于收集要监听的数据。
- <template>
- <h2>当前求和为:{{sum}}h2>
- <button @click="sum++">点我+1button>
- <hr>
- <h2>姓名:{{person.name}}h2>
- <h2>年龄:{{person.age}}h2>
- <h2>薪资:{{person.job.j1.salary}}Kh2>
- <button @click="person.name+='~'">修改姓名button>
- <button @click="person.age++">增长年龄button>
- <button @click="person.job.j1.salary++">涨薪button>
- template>
-
- <script>
- import {ref,reactive,watch,watchEffect} from 'vue'
- export default {
- name: 'Demo',
- setup(){
- //数据
- let sum = ref(0)
- let msg = ref('你好啊')
- let person = reactive({
- name:'张三',
- age:18,
- job:{
- j1:{
- salary:20
- }
- }
- })
-
- //监视
- /* watch(sum,(newValue,oldValue)=>{
- console.log('sum的值变化了',newValue,oldValue)
- },{immediate:true}) */
-
- watchEffect(()=>{
- const x1 = sum.value
- const x2 = person.job.j1.salary
- console.log('watchEffect所指定的回调执行了')
- })
-
- //返回一个对象(常用)
- return {
- sum,
- person
- }
- }
- }
- script>
-
销毁阶段的两个名字改了,创建阶段的2个钩子在setup中调用。
- <template>
- <h2>当前求和为:{{sum}}h2>
- <button @click="sum++">点我+1button>
- template>
-
- <script>
- import {ref,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from 'vue'
- export default {
- name: 'Demo',
-
- setup(){
- // 等于 beforeCreate 和 created
- console.log('---setup---')
- //数据
- let sum = ref(0)
-
- //通过组合式API的形式去使用生命周期钩子
- onBeforeMount(()=>{
- console.log('---onBeforeMount---')
- })
- onMounted(()=>{
- console.log('---onMounted---')
- })
- onBeforeUpdate(()=>{
- console.log('---onBeforeUpdate---')
- })
- onUpdated(()=>{
- console.log('---onUpdated---')
- })
- onBeforeUnmount(()=>{
- console.log('---onBeforeUnmount---')
- })
- onUnmounted(()=>{
- console.log('---onUnmounted---')
- })
-
- //返回一个对象(常用)
- return {sum}
- },
- //通过配置项的形式使用生命周期钩子
- //#region
- beforeCreate() {
- console.log('---beforeCreate---')
- },
- created() {
- console.log('---created---')
- },
- beforeMount() {
- console.log('---beforeMount---')
- },
- mounted() {
- console.log('---mounted---')
- },
- beforeUpdate(){
- console.log('---beforeUpdate---')
- },
- updated() {
- console.log('---updated---')
- },
- beforeUnmount() {
- console.log('---beforeUnmount---')
- },
- unmounted() {
- console.log('---unmounted---')
- },
- //#endregion
- }
- script>
-
- //祖组件
- setup(){
- ......
- let car = reactive({name:'奔驰',price:'40万'})
- provide('car',car)
- ......
- }
-
- //后代组件
- setup(props,context){
- ......
- const car = inject('car')
- return {car}
- ......
- }
本质是一个函数,把setup函数中使用的Composition API进行了封装,类似于vue2.x中的mixin。
- import {reactive,onMounted,onBeforeUnmount} from 'vue'
- export default function (){
- //实现鼠标“打点”相关的数据
- let point = reactive({
- x:0,
- y:0
- })
-
- //实现鼠标“打点”相关的方法
- function savePoint(event){
- point.x = event.pageX
- point.y = event.pageY
- console.log(event.pageX,event.pageY)
- }
-
- //实现鼠标“打点”相关的生命周期钩子
- onMounted(()=>{
- window.addEventListener('click',savePoint)
- })
-
- onBeforeUnmount(()=>{
- window.removeEventListener('click',savePoint)
- })
-
- return point
- }
-
- <template>
- <h2>当前求和为:{{sum}}h2>
- <button @click="sum++">点我+1button>
- <hr>
- <h2>当前点击时鼠标的坐标为:x:{{point.x}},y:{{point.y}}h2>
- template>
-
- <script>
- import {ref} from 'vue'
- import usePoint from '../hooks/usePoint'
- export default {
- name: 'Demo',
- setup(){
- //数据
- let sum = ref(0)
- let point = usePoint()
-
-
- //返回一个对象(常用)
- return {sum,point}
- }
- }
- script>
-
-
vue3定义数据和使用数据放在一起进行处理,更加易读和方便处理。
shallowReactive 与 shallowRef
shallowReactive:只处理对象最外层属性的响应式(浅响应式)。
shallowRef:只处理基本数据类型的响应式, 不进行对象的响应式处理。
readonly 与 shallowReadonly
readonly: 让一个响应式数据变为只读的(深只读)。
shallowReadonly:让一个响应式数据变为只读的(浅只读)。
toRaw 与 markRaw
toRaw:将一个由reactive生成的响应式对象转为普通对象
markRaw:标记一个对象,使其永远不会再成为响应式对象。
customRef
- <template>
- <input type="text" v-model="keyWord">
- <h3>{{keyWord}}h3>
- template>
-
- <script>
- import {ref,customRef} from 'vue'
- export default {
- name: 'App',
- setup() {
- //自定义一个ref——名为:myRef
- function myRef(value,delay){
- let timer
- return customRef((track,trigger)=>{
- return {
- get(){
- console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
- track() //通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的)
- return value
- },
- set(newValue){
- console.log(`有人把myRef这个容器中数据改为了:${newValue}`)
- clearTimeout(timer)
- timer = setTimeout(()=>{
- value = newValue
- trigger() //通知Vue去重新解析模板
- },delay)
- },
- }
- })
- }
-
- // let keyWord = ref('hello') //使用Vue提供的ref
- let keyWord = myRef('hello',500) //使用程序员自定义的ref
-
- return {keyWord}
- }
- }
- script>
-
响应式数据的判断
isRef: 检查一个值是否为一个 ref 对象
isReactive: 检查一个对象是否是由 `reactive` 创建的响应式代理
isReadonly: 检查一个对象是否是由 `readonly` 创建的只读代理
isProxy: 检查一个对象是否是由 `reactive` 或者 `readonly` 方法创建的代理
Fragment
在Vue2中: 组件必须有一个根标签
在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中
好处: 减少标签层级, 减小内存占用
Teleport
`Teleport` 是一种能够将我们的组件html结构移动到指定位置的技术。
- <teleport to="body">
- <div v-if="isShow" class="mask">
- <div class="dialog">
- <h3>我是一个弹窗h3>
- <button @click="isShow = false">关闭弹窗button>
- div>
- div>
- teleport>
Suspense
异步渲染子组件:通过插槽控制显示
- <template>
- <div class="app">
- <h3>我是App组件h3>
- <Suspense>
- <template v-slot:default>
- <Child/>
- template>
- <template v-slot:fallback>
- <h3>稍等,加载中...h3>
- template>
- Suspense>
- div>
- template>
-
- <script>
- // import Child from './components/Child'//静态引入
- import {defineAsyncComponent} from 'vue'
- const Child = defineAsyncComponent(()=>import('./components/Child')) //异步引入
- export default {
- name:'App',
- components:{Child},
- }
- script>
-
- <style>
- .app{
- background-color: gray;
- padding: 10px;
- }
- style>
通过 按需导入的
createStore
方法来来构建store
实例通过 按需导入的
createRouter
方法来构建router
实例
子组件需要声明自定义事件;
可以用computed实现
按需实现深度监听
2. PatchFlag & hoistStatic & cacheHandler
https://vue-next-template-explorer.netlify.app
PatchFlag:编译模板时,给动态节点做标记,如TEXT/ CLASS/ PROPS。diff算法根据标记做对比。
hoistStatic:将静态节点的定义,提升到父作用域,缓存起来;多个相邻的静态节点合并。
cacheHandler:事件缓存。
3.SSR优化
静态节点直接输出,绕过了vdom
4. tree shaking
编译时,根据不同的情况,引入不同的API
- import { ref } from 'vue'
- import Child from './Child'
-
- export default {
- components: { Child },
- setup() {
- const countRef = ref(200)
- //函数中使用jsx
- const render = () => {
- return <>
- <p>demo1 {countRef.value}p> // jsx
- <Child a={1}/>
- >
- }
- return render
- }
- }
-
- //.jsx文件
- import { defineComponent } from 'vue'
-
- export default defineComponent({//传递setup函数或组件配置
- props: ['a'],
- setup(props) {
- const render = () => {
- return <p>Child {props.a}p>
- }
- return render
- }
- })
JSX本质是js代码,可以使用js的任何能力
template只能嵌入简单的js表达式,其他需要指令,如v-if
template | JSX | |
插值 | {{ msg }} | { msg } |
组件定义 | 大小写 | 大写 |
属性 | :name="name" | name={name} |
事件 | @click="demo(123)" | onClick={()=>demo(123)} |
条件 | v-if | {flagRef.value && |
循环 | v-for | {state.list.map(item => |
插槽 | v-slot | 通过函数传参实现 |
- import { defineComponent } from 'vue'
- import Child from './Child'
-
- export default defineComponent(() => {
- function render(msg) {
- return <p>msg: {msg} 123123p>
- }
-
- return () => {
- return <>
- <p>Demo - JSXp>
- <Child render={render}>Child>
- >
- }
- })
-
-
- import { defineComponent, ref } from 'vue'
-
- export default defineComponent({
- props: ['render'],
- setup(props) {
- const msgRef = ref('作用域插槽 Child - JSX')
-
- return () => {
- return <p>{props.render(msgRef.value)}p>
- }
- }
- })
定义属性defineProps;定义事件defineEmits;defineExpose暴露数据给父组件
- function add(a, b) { return a + b }
-
- <script setup>
- //无setup函数,无需return
- import { ref, reactive, toRefs, onMounted } from 'vue'
- //组件无需注册
- import Child1 from './Child1'
- import Child2 from './Child2'
- import Child3 from './Child3'
- //ref
- const countRef = ref(100)
- function addCount() {
- countRef.value++
- }
- //reactive
- const state = reactive({
- name: '双越'
- })
- const { name } = toRefs(state)
- //和其他script同时使用
- console.log( add(10, 20) )
- //触发自定义事件
- function onChange(info) {
- console.log('on change', info)
- }
- function onDelete(info) {
- console.log('on delete', info)
- }
- //获取子组件数据
- const child3Ref = ref(null)
- onMounted(() => {
- // 拿到 Child3 组件的一些数据
- console.log(child3Ref.value)
- console.log(child3Ref.value.a)
- console.log(child3Ref.value.b)
- })
-
- script>
-
-
{{countRef}}
-
{{name}}
-
-
-
-
-
-
-
-
-
- Child2.vue
- import { defineProps, defineEmits } from 'vue'
-
- // 定义属性:defineProps
- const props = defineProps({
- name: String,
- age: Number
- })
-
- // 定义事件:defineEmits
- const emit = defineEmits(['change', 'delete'])
- function deleteHandler() {
- emit('delete', 'aaa')
- }
-
-
-
Child2 - name: {{props.name}}, age: {{props.age}}
-
-
-
-
- Child3.vue
- import { ref, defineExpose } from 'vue'
-
- const a = ref(101)
- const b = 201
- //子向父组件传值:defineExpose
- defineExpose({
- a,
- b
- })
-
-
-
Child3
ts的主要优势在于静态类型检查和环境声明