Vue3 知识点总结

Vue3 新功能

createApp

// vue2 初始化实例
const app = new Vue({ /* 选项 */ });

// vue3 初始化实例
const app = Vue.createApp({ /* 选项 */ });
// vue2
Vue.use(/* ... */);
Vue.mixin(/* ... */);
Vue.component(/* ... */);
Vue.directive(/* ... */);

// vue3
app.use(/* ... */);
app.mixin(/* ... */);
app.component(/* ... */);
app.directive(/* ... */);

emits 属性

两种形式声明触发的事件:

  • 使用字符串数组的简易形式。
  • 使用对象的完整形式。该对象的每个属性键是事件的名称,值是 null 或一个验证函数。

<template>
	
	<HelloWorld :msg="msg" @onSayHello="sayHello" />
template>

<script>
	export default {
		name: 'Helloworld',
		props: {
			msg: String
		},
		// 声明父组件传递的事件
		emits: ['onSayHello']setup(props, { emit }) {
			// 引用
			emit('onSayHello', 'bbb');
		}
	}
script>
// 对象语法
export default {
    emits: {
        // 没有验证函数
        click: null,

        // 具有验证函数
	    submit: (payload) => {
	        if (payload.email && payload.password) {
	            return true
	        } else {
	            console.warn(`Invalid submit event payload!`)
	            return false
	        }
	    }
    }
}

多事件处理


<button @click="one($event), two($event)">Submitbutton>

Fragment

  • vue2 模板里面只能是单一节点;
  • vue3 里面可以是多个节点

<template>
	<div class="blog-post">
		<h3>{{ title }}h3>
		<div v-html="content">div>
	div>
template>

<template>
	<h3>{{ title }}h3>
	<div v-html="content">div>
template>

移除 .sync 改为 v-model 参数


<MyComponent v-bind:title.sync="title" />


<MyComponent 
	v-bind:title="title" 
	v-on:update:title="title = $event" 
/> 

<MyComponent v-model:title="title" /> 


<MyComponent :title="title" @update:title="title = $event" /> 

异步组件的引用方式

// vue2
new Vue({
	// ...
	components: {
		'my-component': () => import('./my-async-component.vue')
	}
});
// vue3
import { createApp, defineAsyncComponent } from 'vue';

createApp({
	// ...
	components: {
		AsyncComponent: defineAsyncComponent(() => import('./my-async-component.vue'))
	}
});

移除 filter




{{ message | capitalize }}


<div v-bind:id="rawId | fomatId">div>

Teleport

把弹窗放到组件外面去。



<button @click="modalOpen = true">
	Open full screen modal!(With teleport!)
button>

<teleport to="body">
	<div v-if="modalOpen" class="modal">
		<div>
			teleport 弹窗(父元素是 body)
			<button @click="modalOpen = false">Closebutton>
		div>
	div>
teleport>

Suspense

<Suspense>
	<template>
		
		<Test1 />
	template>

	
	<template #fallback>
		loading...
	template>
Suspense>

composition API

composition API 带来了什么?

  • 更好的代码组织
  • 更好的逻辑复用
  • 更好的类型推导
  1. 更好的代码组织、更好的逻辑复用
    Option API 代码逻辑是分散的;组合式API 代码逻辑是组合到一起的。

出处:https://coding.imooc.com/lesson/419.html
Vue3 知识点总结_第1张图片

  1. 更好的类型推导
    Options API 不利于推导类型和属性。
{
	data() {
		return {
			a: 10
		};
	}
	methods: {
		fn1() {
			const a = this.a;
		}
	},
	mounted() {
		this.fn1(); // 获取 a
		// 不利于推导类型和属性
	}
}

Composition API 函数在哪定义,传入什么,返回什么都是很清晰的。

出处:https://coding.imooc.com/lesson/419.html
Vue3 知识点总结_第2张图片
出处:https://coding.imooc.com/lesson/419.html
Vue3 知识点总结_第3张图片

reactive

通过它来创建引用类型的响应式数据。

ref、toRef、toRefs

ref
  • 生成值类型的响应式数据
  • 可用于模板和 reactive
  • 通过 .value 修改值
  • 可以获取 dom 节点
<template>
    <p>ref demo {{ageRef}} {{state.name}}p>
template>

<script>
import { ref, reactive } from 'vue';

export default {
    name: 'Ref',
    setup() {
        const ageRef = ref(20); // 值类型 响应式
        const nameRef = ref('章三');

        const state = reactive({
            name: nameRef
        });

        setTimeout(() => {
            console.log('ageRef', ageRef.value);

            ageRef.value = 25; // .value 修改值
            nameRef.value = '章三A';
        }, 1500);

        return {
            ageRef,
            state
        };
    }
}
script>
<template>
    <p ref="elemRef">我是一行文字p>
template>

<script>
import { ref, onMounted } from 'vue'

export default {
    name: 'RefTemplate',
    setup() {
        const elemRef = ref(null)

        onMounted(() => {
            console.log('ref template', elemRef.value.innerHTML, elemRef.value)
        })

        return {
            elemRef
        }
    }
}
script>
toRef
  • 针对一个响应式对象(reactive 封装)的 prop
  • 常见一个 ref,具有响应式
  • 两者保持引用关系(一个改了,另一个也同步被改)
<template>
    <p>toRef demo - {{ageRef}} - {{state.name}} {{state.age}}p>
template>

<script>
import { toRef, reactive } from 'vue';

export default {
    name: 'ToRef',
    setup() {
        const state = reactive({
            age: 20,
            name: '章三'
        });
        
        // // toRef 如果用于普通对象(非响应式对象),产出的结果不具备响应式
        // const state = {
        //     age: 20,
        //     name: '章三'
        // };

        const ageRef = toRef(state, 'age');

        setTimeout(() => {
            state.age = 25;
        }, 1500);

        setTimeout(() => {
            ageRef.value = 30; // .value 修改值
        }, 3000);

        return {
            state,
            ageRef
        };
    }
}
script>

普通对象要实现响应式,用 reactive;对象的某个属性要实现响应式,用 toRef

toRefs
  • 将响应式对象(reactive 封装)转换成普通对象
  • 对象的每个 prop 都是对应的 ref
  • 两者保持引用关系
  • 合成函数返回响应式对象
<template>
    <p>toRefs demo {{age}} {{name}}p>
template>

<script>
import { toRefs, reactive } from 'vue';

export default {
    name: 'ToRefs',
    setup() {
        const state = reactive({
            age: 20,
            name: '章三'
        })

        const stateAsRefs = toRefs(state) // 将响应式对象,变成普通对象

        // const { age: ageRef, name: nameRef } = stateAsRefs; // 每个属性,都是 ref 对象
        // return {
        //     ageRef,
        //     nameRef
        // };

        setTimeout(() => {
            state.age = 25;
        }, 1500);

        return stateAsRefs;

		// 正常写是 return { state }; 使用时是 {{state.name}}  {{state.age}}
		// 如果想直接使用 {{name}}  {{age}} 就需要解构 state
		// 直接解构 state 会失去响应式
		// return {
		// 	...state
		// };
    }
}
script>
// 合成函数返回响应式对象
function useFeatureX() {
	const state = reactive({
		x: 1,
		y: 2
	});
	
	// 逻辑运行状态,省略 n 行

	// 返回时转换为 ref
	return toRefs(state);
}

export default {
	setup() {
		// 可以在不失去响应式的情况下破坏结构
		const { x, y } = useFeatureX();

		return {
			x,
			y
		};
	}
}
最佳使用方式
  • reactive 做对象的响应式,用 ref 做值类型响应式
  • setup 中返回 toRefs(state),或者 toRef(state, 'xxx')
  • ref 的变量命名都用 xxxRef
  • 合成函数返回响应式对象时,用 toRefs
为什么需要使用 ref ?
  • 返回值类型,会丢失响应式
  • 如在 setupcomputed、合成函数,都有可能返回值类型
  • Vue 如果不定义 ref,用户将自造 ref,反而混乱
为何需要 .value ?
  • ref 是一个对象,才能不丢失响应式,value 用来存储值
  • 通过 .value 属性的 getset 实现响应式
  • 用于模板、reacttive 时,不需要 .value,其他情况都需要

ref 也是靠 reactive 实现的。类似于 ref = reactive({ value: '张三' }) ;

为什么需要 toRef 和 toRefs ?

初衷:在不丢失响应式的情况下,把对象数据分解/扩散
前提:针对的是 reactive 封装的响应式对象,并非普通对象。

reactive、ref 是创造响应式;toReftoRefs 是延续响应式。

readonly

创建一个只读的数据。

setup

setup 中如何获取组件实例
  • setup 和其他 Composition API 中没有 this
  • 可通过 getCurrentInstance 获取当前实例
  • 若使用 Options API 可照常使用 this
<template>
    <p>get instancep>
template>

<script>
import { onMounted, getCurrentInstance } from 'vue';

export default {
    name: 'GetInstance',
    data() {
        return {
            x: 1,
            y: 2
        };
    },
    setup() {
        console.log('this1', this); // undefined

        onMounted(() => {
            console.log('this in onMounted', this);
            console.log('x', instance.data.x); // 1 应该放在 onMounted 这里获取
        });

        const instance = getCurrentInstance();
        
        console.log('instance', instance); 
        console.log('x', instance.data.x); // undefined  因为 setup 等于 beforeCreate 和 created 此时还没有初始化完成 所以是 undefined
    },
    mounted() {
        console.log('this2', this); // Proxy
        console.log('y', this.y); // 2
    }
}
script>

watch、watchEffect

watchwatchEffect 的区别:

  • 两者都可监听 data 属性变化
  • watch 需要明确监听哪个属性
  • watchEffect 会根据其中的属性,自动监听其变化
<template>
    <p>watch vs watchEffectp>
    <p>{{numberRef}}p>
    <p>{{name}} {{age}}p>
template>

<script>
// watch 监听
import { reactive, ref, toRefs, watch } from 'vue';

export default {
    name: 'Watch',
    setup() {
        const numberRef = ref(100);
        
        const state = reactive({
            name: '章三',
            age: 20
        });

        watch(
        	numberRef, 
        	(newNumber, oldNumber) => {
            	console.log('ref watch', newNumber, oldNumber);
        	}, 
        	{
            	immediate: true; // 初始化之前就监听,可选
	        }
        );

        setTimeout(() => {
            numberRef.value = 200;
        }, 1500);

        watch(
	        // 第一个参数,确定要监听哪个属性
	        () => state.age,
	        // 第二个参数,回调函数
	        (newAge, oldAge) => {
        		console.log('state watch', newAge, oldAge);
	        },
	        // 第三个参数,配置项
	        {
	        	immediate: true, // 初始化之前就监听,可选
	        	// deep: true // 深度监听
	        }
        );

        setTimeout(() => {
           state.age = 25;
        }, 1500);
        
        setTimeout(() => {
            state.name = '章三A';
        }, 3000);

        return {
            numberRef,
            ...toRefs(state);
        };
    }
}
script>
<template>
    <p>watch vs watchEffectp>
    <p>{{numberRef}}p>
    <p>{{name}} {{age}}p>
template>

<script>
// watchEffect 监听
import { reactive, ref, toRefs, watchEffect } from 'vue';

export default {
    name: 'Watch',
    setup() {
        const numberRef = ref(100);
        
        const state = reactive({
            name: '章三',
            age: 20
        });

        watchEffect(() => {
            // 初始化时,一定会执行一次(收集要监听的数据)
            console.log('hello watchEffect');
        });
        
        watchEffect(() => {
        	// 写了哪个就监听哪个
            console.log('state.name', state.name);
        });
        
        watchEffect(() => {
            console.log('state.age', state.age);
        });
        
        // watchEffect(() => {
            console.log('state.age', state.age);
            console.log('state.name', state.name);
        });
        
        setTimeout(() => {
            state.age = 25;
        }, 1500);
        
        setTimeout(() => {
            state.name = '章三A';
        }, 3000);

        return {
            numberRef,
            ...toRefs(state);
        };
    }
}
script>

Composition API 如何实现逻辑复用

  • 抽离逻辑代码到一个函数
  • 函数命名约定为 useXxxx 格式(React Hooks 也是)
  • setup 中引用 useXxxx 函数
// useMousePosition.js
import { reactive, ref, onMounted, onUnmounted } from 'vue';

function useMousePosition() {
    const x = ref(0);
    const y = ref(0);

    function update(e) {
        x.value = e.pageX;
        y.value = e.pageY;
    }

    onMounted(() => {
        console.log('useMousePosition mounted');
        window.addEventListener('mousemove', update);
    });

    onUnmounted(() => {
        console.log('useMousePosition unMounted');
        window.removeEventListener('mousemove', update);
    });

    return {
        x,
        y
    };
}

// function useMousePosition2() {
//     const state = reactive({
//         x: 0,
//         y: 0
//     });

//     function update(e) {
//         state.x = e.pageX;
//         state.y = e.pageY;
//     }

//     onMounted(() => {
//         console.log('useMousePosition mounted');
//         window.addEventListener('mousemove', update);
//     });

//     onUnmounted(() => {
//         console.log('useMousePosition unMounted');
//         window.removeEventListener('mousemove', update);
//     });

//     return state;
// }

export default useMousePosition;
// export default useMousePosition2;
<template>
    <p>mouse position {{x}} {{y}}p>
template>

<script>
import useMousePosition from './useMousePosition';
// import useMousePosition2 from './useMousePosition';

export default {
    name: 'MousePosition',
    setup() {
        const { x, y } = useMousePosition();

        return {
            x,
            y
        };

        // const state = useMousePosition2();
        
        // return {
        //     state
        // };
    }
}
script>

Composition API 和 React Hooks 的对比

  • Composition APIsetup 只会被调用一次,而 React Hooks 函数会被多次调用
  • Composition API 无需 useMemouseCallback,因为 setup 只调用一次
  • Composition API 无需顾虑调用顺序,而 React Hooks 需要保证 hooks 的顺序一致
  • Composition API 的 reacttive + refReact HooksuseState 要难理解

生命周期

vue3 给了两种生命周期的方式,可以用 Options API ,也可以用 Composition API

Options API 生命周期:
  • beforeDestroy 改为 beforeUnmount
  • destroyed 改为 unmounted
  • 其他沿用 vue2 的生命周期
  • vue3 中可以使用 vue2 的生命周期
<template>
    <p>生命周期 {{msg}}p>
template>

<script>
export default {
    name: 'LifeCycles',

    props: {
        msg: String
    },
    
    beforeCreate() {
        console.log('beforeCreate')
    },
    created() {
        console.log('created')
    },
    beforeMount() {
        console.log('beforeMount')
    },
    mounted() {
        console.log('mounted')
    },
    beforeUpdate() {
        console.log('beforeUpdate')
    },
    updated() {
        console.log('updated')
    },
    // beforeDestroy 改名
    beforeUnmount() {
        console.log('beforeUnmount')
    },
    // destroyed 改名
    unmounted() {
        console.log('unmounted')
    }
}
script>
Composition API 生命周期:
  • setup:等于 beforeCreatecreated
  • onBeforeMount
  • onMounted
  • onBeforeUpdate
  • onUpdated
  • onBeforeUnmount
  • onUnmounted
<template>
    <p>生命周期 {{msg}}p>
template>

<script>
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'

export default {
    name: 'LifeCycles',

    props: {
        msg: String
    },

    // 等于 beforeCreate 和 created
    setup() {
        console.log('setup')

        onBeforeMount(() => {
            console.log('onBeforeMount')
        })
        onMounted(() => {
            console.log('onMounted')
        })
        onBeforeUpdate(() => {
            console.log('onBeforeUpdate')
        })
        onUpdated(() => {
            console.log('onUpdated')
        })
        onBeforeUnmount(() => {
            console.log('onBeforeUnmount')
        })
        onUnmounted(() => {
            console.log('onUnmounted')
        })
    },
}
script>

原理

Proxy 实现响应式

背景

Object.defineProperty 的缺点:

  • 深度监听,需要递归到底,一次性计算量很大
  • 无法监听新增属性、删除属性(所以需要 Vue.setVue.delete 这两个 API去 做这个事情)
  • 无法原生监听数组,需要特殊处理

Proxy 基本使用

Reflect 是和 Proxy 配合使用的。

  • proxy 可以看做是一个拦截器,会拦截对某个属性的操作;
  • Reflect 是提供操作对象的 api

Reflect 的作用:

  • Proxy 能力一一对应
  • 规范化、标准化、函数式
const data = {
	name: 'zhangsan',
	age: 20
};
const proxyData = new Proxy(data, {
	get(target, key, recevier) {
		// get 方法和直接读取对象的属性实现的效果是一样的,
		// 但直接读取对象的属性是一种魔法,不符合函数式编程的理念,
		// 而 get 方法是一种基于底层的 API,更符合理念。
		const result = Reflect.get(target, key, recevier);

		console.log('get', key);
		return result; // 返回结果
	},
	set(target, key, val, recevier) {
		const result = Reflect.set(target, key, val, recevier);

		console.log('set', key, val);
		return result; // 是否设置成功 true / false
		
	},
	deleteProperty(target, key) {
		const result = Reflect.deleteProperty(target, key);

		console.log('delete property', key);
		return result; //  是否删除成功 true / false
	}
});
// 测试监听数组
const data = ['a', 'b', 'c'];

const proxyData = new Proxy(data, {
    get(target, key, receiver) {
        // 只处理本身(非原型的)属性
        const ownKeys = Reflect.ownKeys(target); // 获取不是原型的属性
        
        if (ownKeys.includes(key)) {
            console.log('get', key); // 监听
        }

        const result = Reflect.get(target, key, receiver);
        
        return result; // 返回结果
    },
    set(target, key, val, receiver) {
        // 重复的数据,不处理
        if (val === target[key]) {
            return true;
        }

        const result = Reflect.set(target, key, val, receiver);
        
        console.log('set', key, val);
        // console.log('result', result); // true
        
        return result; // 是否设置成功
    },
    deleteProperty(target, key) {
        const result = Reflect.deleteProperty(target, key);
        
        console.log('delete property', key);
        // console.log('result', result) // true
        
        return result; // 是否删除成功
    }
});

vue3 如何实现响应式

使用 Proxy 实现响应式:能规避 Object.defineProperty 的问题。

  • 深度监听,性能更好
  • 可监听 新增/删除 属性
  • 可监听数组变化

缺点:
Proxy 无法兼容所有浏览器,无法 polyfill

// 创建响应式
function reactive(target = {}) {
    if (typeof target !== 'object' || target == null) {
        // 不是对象或数组,则返回
        return target;
    }

    // 代理配置
    const proxyConf = {
        get(target, key, receiver) {
            // 只处理本身(非原型的)属性
            const ownKeys = Reflect.ownKeys(target);
            
            if (ownKeys.includes(key)) {
                console.log('get', key); // 监听
            }
    
            const result = Reflect.get(target, key, receiver);
        
            // 深度监听
            return reactive(result);
        },
        set(target, key, val, receiver) {
            // 重复的数据,不处理
            if (val === target[key]) {
                return true;
            }
    
            const ownKeys = Reflect.ownKeys(target);
            
            if (ownKeys.includes(key)) {
                console.log('已有的 key', key);
            } else {
                console.log('新增的 key', key);
            }

            const result = Reflect.set(target, key, val, receiver);
            
            console.log('set', key, val);
            // console.log('result', result); // true
            
            return result; // 是否设置成功
        },
        deleteProperty(target, key) {
            const result = Reflect.deleteProperty(target, key);
            
            console.log('delete property', key);
            // console.log('result', result); // true
            
            return result; // 是否删除成功
        }
    };

    // 生成代理对象
    const observed = new Proxy(target, proxyConf);
    
    return observed;
}

// 测试数据
const data = {
    name: 'zhangsan',
    age: 20,
    info: {
        city: 'beijing',
        a: {
            b: {
                c: {
                    d: {
                        e: 100
                    }
                }
            }
        }
    }
};

const proxyData = reactive(data);

Proxy 深度监听,性能如何提升的?
之前 Object.defineProperty 是一开始就进行递归了,没有任何条件;而 Proxy 是在 get 的时候才进行递归,获取到哪一层哪一层才触发响应式,没有获取到那一层的时候不会触发响应式。

编译优化

PatchFlag 静态标记

  • 编译模板时,动态节点做标记
  • 标记 分为不同的类型,如 TEXTPROPS
  • diff 算法时,可以区分静态节点,以及不同类型的动态节点

可以区分出动态和静态节点,静态节点就不用比较了,因为静态节点是不会变的,这样就可以优化 diff 算法。(从输入来源做的优化,并不是针对 diff 算法这个流程做的优化)

出处:https://coding.imooc.com/lesson/419.html
Vue3 知识点总结_第4张图片

hoistStatic 静态提升

  • 将静态节点的定义,提升到父作用域,缓存起来
  • 多个相邻的静态节点,会被合并起来
  • 典型的拿空间换时间的优化策略
    Vue3 知识点总结_第5张图片
    以上三个静态节点定义到父作用域,以后这个函数怎么执行,都不会重新定义这个三个变量。(空间换时间)
    Vue3 知识点总结_第6张图片
    Vue3 知识点总结_第7张图片
    如果相邻的静态节点很多,到一定程度后,就会合并起来做一个静态的集合,只定义一个变量。(不是空间换时间,类似于合并,提前帮我们做的操作)
    Vue3 知识点总结_第8张图片

cacheHandler 缓存事件

  • 缓存事件
    Vue3 知识点总结_第9张图片
    有缓存就去拿缓存,没有缓存就把这个事件定义并缓存。
    Vue3 知识点总结_第10张图片

SSR 优化

Vue3 知识点总结_第11张图片
开启 ssr 后直接渲染字符串,没有经过 vdom 的一个转换。
Vue3 知识点总结_第12张图片

Tree-shaking 优化

  • 编译时,根据不同的情况,引入不同的 API

根据模板的指令,选择需要 importapi,需要才引入
编译时尽可能的减少体积,从而进行优化
Vue3 知识点总结_第13张图片
Vue3 知识点总结_第14张图片

Vite

Vite 是什么?

Vite 是一个轻量级的、速度极快的构建工具。

浏览器的原生 ES Modules 能力允许在不将代码打包到一起的情况下运行 JavaScript 应用。Vite 的核心理念,就是借助浏览器原生 ES Modules 能力,当浏览器发出请求时,为浏览器按需提供 ES Module 文件,浏览器获取 ES Module 文件会直接执行。

Vite 为什么启动快?

  • 开发环境使用 ES6 Module,无需打包(非常快)
  • 生产环境使用 roolup,并不会快很多

开发环境下,由于浏览器原生 ES6 Modules 的支持,当浏览器发出请求时,Vite 可以在不将源码打包为一个 Bundle 文件的情况下,将源码文件转化为 ES Modules 文件之后返回给浏览器。这样 Vite 的应用启动和热更新 HMR 时的速度都不会随着应用规模的增加而变慢。

ES6 module

基本使用


DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES Module demotitle>
head>
<body>
    <p>基本演示p>

    <script type="module">
        import add from './src/add.js';

        const res = add(1, 2);
        console.log('add res', res);
    script>
body>
html>
// print.js
export default function (a, b) {
    console.log(a, b);
}
// add.js
import print from './print.js';

export default function add(a, b) {
    print('print', 'add');
    
    return a + b;
}

Vue3 知识点总结_第15张图片
Vite 直接使用 ES6 Module 这种模块化的形式去执行;
Webpack 要把 ES6 打包成 ES5

在浏览器中的应用

// print.js
export default function (a, b) {
    console.log(a, b)
}
// add.js
import print from './print.js'

export default function add(a, b) {
    print('print', 'add')
    return a + b
}
// math.js
export function add(a, b) {
    return a + b
}

export function multi(a, b) {
    return a * b
}
// index.js
import { add, multi } from './math.js'
console.log('add res', add(10, 20))
console.log('multi res', multi(10, 20))
外链

DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES Module demotitle>
head>
<body>
    <p>外链p>

    <script type="module" src="./src/index.js">script>
body>
html>
远程引用
DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES Module demotitle>
head>
<body>
    <p>远程引用p>

    <script type="module">
        import { createStore } from 'https://unpkg.com/redux@latest/es/redux.mjs'
        console.log('createStore', createStore)
    script>
body>
html>
动态引用
DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ES Module demotitle>
head>
<body>
    <p>动态引入p>
    <button id="btn1">load1button>
    <button id="btn2">load2button>

    <script type="module">
        document.getElementById('btn1').addEventListener('click', async () => {
            const add = await import('./src/add.js')
            const res = add.default(1, 2)
            console.log('add res', res)
        })
        document.getElementById('btn2').addEventListener('click', async () => {
            const { add, multi } = await import('./src/math.js')
            console.log('add res', add(10, 20))
            console.log('multi res', multi(10, 20))
        })
    script>
body>
html>

Vite 和 Webpack 的区别

  1. Webpack
  • 支持的模块规范:ES ModulesCommonJSAMD Modules
  • Dev Server:通过 webpack-dev-server 托管打包好的模块
  • 生产环境构建:webpack
  1. Vite
  • 支持的模块规范:ES Modules
  • Dev Server:原生 ES Modules,浏览器托管执行
  • 生产环境构建:Rollup

Vue3 和 JSX

JSX 最早是 React 提出的概念。

JSX 基本使用

  • 使用 .jsx 格式的文件和 defineComponent
// child.jsx
import { defineComponent } from 'vue'

export default defineComponent({
    props: ['a'],
    setup(props) {
        const render = () => {
            return <p>Child {props.a}</p>
        }
        return render
    }
})

<template>
    <p @click="changeFlag">Demo {{flagRef}}p>
    <child a="abc" v-if="flagRef">child>
    <ul>
        <li v-for="item in state.list" :key="item">{{item}}li>
    ul>
template>

<script>
import { ref, reactive } from 'vue'
import Child from './Child'

export default {
    name: 'Demo',
    components: { Child },
    setup() {
        const flagRef = ref(true)

        function changeFlag() {
            flagRef.value = !flagRef.value
        }

        const state = reactive({
            list: ['a', 'b', 'c']
        })

        return {
            flagRef,
            changeFlag,
            state
        }
    }
}
script>

<script>
import { ref } from 'vue'
import Child from './Child'

export default {
    components: { Child },
    setup() {
        const countRef = ref(200)

        const render = () => {
            return <p>demo1 {countRef.value}</p> // jsx
        }
        return render
    }
}
script>
// Demo1.jsx
import { defineComponent, ref, reactive } from 'vue'
import Child from './Child'

export default defineComponent(() => {
    const flagRef = ref(true)

    function changeFlag() {
        flagRef.value = !flagRef.value
    }

    const state = reactive({
        list: ['a1', 'b1', 'c1']
    })

    const render = () => {
        return <>
            <p onClick={changeFlag}>demo1 {flagRef.value.toString()}</p>
            {flagRef.value && <Child a={flagRef.value}></Child>}

            <ul>
            {state.list.map(item => <li>{item}</li>)}
            </ul>
        </>
    }
    return render
})

// 1. setup 函数
// 2. 组件的配置

JSX 和 template 的区别

  • 语法上有很大区别:

    • JSX 本质上就是 js 代码,可以使用 js 的任何能力
    • template 只能嵌入简单的 js 表达式,其他需要指令,如 v-if
    • JSX 已经成为 ES 规范, template 还是 Vue 自家规范
  • 本质是相同的:

    • 都会被编译为 js 代码(render 函数)
  • 具体示例:插值,自定义组件,属性和事件,条件和循环

JSX 和 Slot

  • slot 是 Vue 发明的概念,为了完善 template 的能力
  • slot 对初学者难理解,特别是作用域插槽
  • 但使用 JSX 将很容易理解,因为 JSX 本质就是 js

vue3 template 实现 tabs


<template>
    <tabs default-active-key="1" @change="onTabsChange">
        <tab-panel key="1" title="title1">
            <div>tab panel content 1div>
        tab-panel>
        <tab-panel key="2" title="title2">
            <div>tab panel content 2div>
        tab-panel>
        <tab-panel key="3" title="title3">
            <div>tab panel content 3div>
        tab-panel>
    tabs>
template>

<script>
import Tabs from './Tabs'
import TabPanel from './TabPanel'

export default {
    components: { Tabs, TabPanel },
    methods: {
        onTabsChange(key) {
            console.log('tab changed', key)
        }
    },
}
script>

<template>
    <slot>slot>
template>

<script>
export default {
    name: 'TabPanel',
    props: ['key', 'title'],
}
script>

<template>
    <div>
        
        <button
            v-for="titleInfo in titles"
            :key="titleInfo.key"
            :style="{ color: titleInfo.key === actKey ? 'blue' : '#333' }"
            @click="changeActKey(titleInfo.key)"
        >
            {{titleInfo.title}}
        button>
    div>

    <slot>slot>
template>

<script>
import { ref } from 'vue'

export default {
    name: 'Tabs',
    props: ['defaultActiveKey'],
    emits: ['change'],
    setup(props, context) {
        const children = context.slots.default()
        const titles = children.map(panel => {
            const { key, title } = panel.props || {}
            return {
                key,
                title
            }
        })

        // 当前 actKey
        const actKey = ref(props.defaultActiveKey)
        function changeActKey(key) {
            actKey.value = key
            context.emit('change', key)
        }

        return {
            titles,
            actKey,
            changeActKey
        }
    }
}
script>

jsx 实现 tabs


<template>
    <tabs default-active-key="1" @change="onTabsChange">
        <tab-panel key="1" title="title1">
            <div>tab panel content 1div>
        tab-panel>
        <tab-panel key="2" title="title2">
            <div>tab panel content 2div>
        tab-panel>
        <tab-panel key="3" title="title3">
            <div>tab panel content 3div>
        tab-panel>
    tabs>
template>

<script>
import Tabs from './Tabs.jsx'
import TabPanel from './TabPanel'

export default {
    components: { Tabs, TabPanel },
    methods: {
        onTabsChange(key) {
            console.log('tab changed', key)
        }
    },
}
script>

<template>
    <slot>slot>
template>

<script>
export default {
    name: 'TabPanel',
    props: ['key', 'title'],
}
script>
// Tabs.jsx
import { defineComponent, ref } from 'vue'

export default defineComponent({
    name: 'Tabs',
    props: ['defaultActiveKey'],
    emits: ['change'],
    setup(props, context) {
        const children = context.slots.default()
        const titles = children.map(panel => {
            const { key, title } = panel.props || {}
            return {
                key,
                title
            }
        })

        // 当前 actKey
        const actKey = ref(props.defaultActiveKey)
        function changeActKey(key) {
            actKey.value = key
            context.emit('change', key)
        }

        // jsx
        const render = () => <>
            <div>
                {/* 渲染 buttons */}
                {titles.map(titleInfo => {
                    const { key, title } = titleInfo
                    return <button
                        key={key}
                        style={{ color: actKey.value === key ? 'blue' : '#333' }}
                        onClick={() => changeActKey(key)}
                    >{title}</button>
                })}
            </div>

            <div>
                {children.filter(panel => {
                    const { key } = panel.props || {}
                    if (actKey.value === key) return true // 匹配上 key ,则显示
                    return false // 否则,隐藏
                })}
            </div>
        </>
        return render
    }
})

vue3 template 实现 作用域插槽


<template>
    <p>childp>
    <slot :msg="msg">slot>
template>

<script>
export default {
    data() {
        return {
            msg: '作用域插槽 Child'
        }
    }
}
script>

<template>
    <child>
        

        <template v-slot:default="slotProps">
            <p>msg: {{slotProps.msg}} 123123p>
        template>
    child>
template>

<script>
import Child from './Child'

export default {
    components: { Child }
}
script>

jsx 实现 作用域插槽

// Child.jsx
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>
        }
    }
})
// Demo.jsx
import { defineComponent } from 'vue'
import Child from './Child'

export default defineComponent(() => {
    function render(msg) {
        return <p>msg: {msg} 123123</p>
    }

    return () => {
        return <>
            <p>Demo - JSX</p>
            <Child render={render}></Child>
        </>
    }
})

Vue3 script setup (3.2 发布)

  • Vue3 引入了 Composition API
  • Composition API 最重要的是 setup 函数

你可能感兴趣的:(vue.js,javascript,前端)