Vue3.0全家桶最全入门指南 - 快速搭建 (1/4)
Vue3.0全家桶最全入门指南 - vue3.0新特性 (2/4)
Vue3.0全家桶最全入门指南 - vue-router@4.x和vuex@4.x (3/4)
Vue3.0全家桶最全入门指南 -3.x跟2.x的其他差异(4/4)
2.x使用构造函数new Vue(...)
创建实例,3.x使用createApp
函数创建实例;
2.x所有属性方法和设置都绑定到全局Vue
对象上,3.x改为绑定到vue
实例下,收紧了scope;
3.x移除了 Vue.config.productionTip
和 Vue.config.keyCodes
配置属性;
javascript 体验AI代码助手复制代码// vue 2.x import Vue from 'vue' import App from './App' import router from './router' import store from './store' Vue.config.ignoredElements = [/^app-/] Vue.use(/* ... */) Vue.mixin(/* ... */) Vue.component(/* ... */) Vue.directive(/* ... */) Vue.prototype.customProperty = () => {} new Vue({ el: '#app', router, store, render: h => h(App) })
--
javascript 体验AI代码助手复制代码// vue 3.x import { createApp } from 'vue' import App from './App.vue' import router from './router' import store from './store' const app = createApp(App) app.config.isCustomElement = tag => tag.startsWith('app-') app.use(/* ... */) app.mixin(/* ... */) app.component(/* ... */) app.directive(/* ... */) app.config.globalProperties.customProperty = () => {} app.use(router).use(store).mount('#app')
在/src/views目录中新建Test.vue
xml 体验AI代码助手复制代码这是一个新页面
在 /src/router/index.js中创建路由
javascript 体验AI代码助手复制代码import { createRouter, createWebHistory } from 'vue-router' import Home from '../views/Home.vue' const routes = [ { path: '/', name: 'Home', component: Home }, { path: '/about', name: 'About', // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. component: () => import(/* webpackChunkName: "about" */ '../views/About.vue') }, { path: '/test', name: 'Test', component: () => import(/* webpackChunkName: "test" */ '../views/Test.vue') } ] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
vue2.x中,所有的数据都在data
方法中定义返回,方法定义在methods
下面,并通过this
调用vue3.x中,所有的代码逻辑将在setup
方法中实现,包括data
、watch
、computed
、methods
、hooks
,并且不再有this
vue3.x setup
方法在组件生命周期内只执行一次,不会重复执行
相比vue2.x中基于OPTIONS
配置的方式,vue3.x基于组合式API的方式语义没有2.x清晰,2.x中data
、methods
、computed
、watch
等都通过不同的scope区分开,看起来很清晰,3.x都放在setup
方法中,对代码组织能力会有更高的要求。
vue2.x使用Composition API可以安装@vue/composition-api,使用基本跟Composition API一样,这里不再赘述
reactive
几乎等价于 2.x 中的 Vue.observable()
API,只是为了避免与 RxJS
中的 observable
混淆而做了重命名
vue3.x的reactive
和ref
取代了vue2.x中的data
数据定义
从下面的代码中可以看到,reactive
处理的是对象的双向绑定,而ref
则可以处理js基础类型的双向绑定,其实ref
的实现原理也只是对基础类型进行对象化封装,再添加一个__v_isRef
标识属性用来区分。
具体可看ref.ts源码,如下
kotlin 体验AI代码助手复制代码class RefImpl{ private _value: T // ref 原始值 public readonly __v_isRef = true // ref 标识 constructor(private _rawValue: T, public readonly _shallow = false) { this._value = _shallow ? _rawValue : convert(_rawValue) } get value() { // getter 收集依赖 track(toRaw(this), TrackOpTypes.GET, 'value') return this._value } set value(newVal) { // setter 触发响应 if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal this._value = this._shallow ? newVal : convert(newVal) trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal) } } }
vue2.x与3.x响应式写法差别:
javascript 体验AI代码助手复制代码// vue2.x export default { name: 'Test', data () { return { count: 0, num: 0 } }, methods: { addCount () { this.count++ } addNum() { this.num++ } } }
----
xml 体验AI代码助手复制代码// vue3.xcount 点击次数: {{ count }} 点击增加num 点击次数: {{ num }} 点击增加
解开 Ref
我们可以将一个 ref
值暴露给渲染上下文,在渲染过程中,Vue 会直接使用其内部的值,也就是说在模板中你可以把 {{ num.value }}
直接写为 {{ num }}
,但是在js中还是需要通过 num.value
取值和赋值。
使用 Reactive
使用 reactive
组合函数时必须始终保持对这个所返回对象的引用以保持响应性。这个对象不能被解构或展开,一旦被解构或者展开,返回的值将失去响应式。
toRefs
API 用来提供解决此约束的办法——它将响应式对象的每个 property 都转成了相应的 ref
。
对于不允许写的对象,不管是普通object
对象、reactive
对象、ref
对象,都可以通过readonly
方法返回一个只读对象
直接修改readonly
对象,控制台会打印告警信息,不会报错
scss 体验AI代码助手复制代码const state = reactive({ count: 0 }) const readonlyState = readonly(state) // 监听只读属性,state.count修改后依然会触发readonlyState.count更新 watch(() => readonlyState.count, (newVal, oldVal) => { console.log('readonly state is changed!') setTimeout(() => { // 修改只读属性会打印告警信息,但是不会报错 readonlyState.count = 666 }, 1000) })
2.x和3.x中的computed
都支持getter和setter,写法一样,只是3.x中是组合函数式
javascript 体验AI代码助手复制代码// vue2.x export default { ... computed: { totalCount() { return this.count + this.num }, doubleCount: { get() { return this.count * 2 }, set(newVal) { this.count = newVal / 2 } } } }
--
dart 体验AI代码助手复制代码// vue3.x import { reactive, ref, toRefs, computed } from 'vue' export default { name: 'Test', setup () { const state = reactive({ count: 0, double: computed(() => { return state.count * 2 }) }) const num = ref(0) const addCount = function () { state.count++ } const addNum = function () { num.value++ } // only getter const totalCount = computed(() => state.count + num.value) // getter & setter const doubleCount = computed({ get () { return state.count * 2 }, set (newVal) { state.count = newVal / 2 } }) return { ...toRefs(state), num, totalCount, doubleCount, addCount, addNum } } }
3.x和2.x的watch
一样,支持immediate
和deep
选项,但3.x不再支持'obj.key1.key2'
的"点分隔"写法;
3.x中watch
支持监听单个属性,也支持监听多个属性,相比2.x的watch
更灵活;
3.x中watchEffect
方法会返回一个方法,用于停止监听;
watch
跟watchEffect
不同的地方在于,watchEffect
注册后会立即调用,而watch
默认不会,除非显示指定immediate=true
,并且watchEffect
可以停止监听
在 DOM 当中渲染内容会被视为一种“副作用”:程序会在外部修改其本身 (也就是这个 DOM) 的状态。我们可以使用 watchEffect
API 应用基于响应式状态的副作用,并自动进行重应用。
javascript 体验AI代码助手复制代码// vue2.x export default { ... data () { return { ... midObj: { innerObj: { size: 0 } } } }, computed: { totalCount() { return this.count + this.num } }, watch: { totalCount(newVal, oldVal) { console.log(`count + num = ${newVal}`) }, 'midObj.innerObj.size': { // deep: true, immediate: true, handler(newVal, oldVal) { console.log(`this.midObj.innerObj.size = ${newVal}`) } } } }
--
javascript 体验AI代码助手复制代码// vue3.x import { reactive, ref, toRefs, computed, watch } from 'vue' export default { name: 'Test', setup () { const state = reactive({ count: 0, double: computed(() => { return state.count * 2 }), midObj: { innerObj: { size: 0 } } }) const num = ref(0) const addCount = function () { state.count++ } const addNum = function () { num.value++ } // 监听单个属性 watch(() => totalCount.value, (newVal, oldVal) => { console.log(`count + num = ${newVal}`) }) // 监听单个属性, immediate watch(() => totalCount.value, (newVal, oldVal) => { console.log(`count + num = ${newVal}, immediate=true`) }, { immediate: true }) // 监听单个属性, deep watch(() => state.midObj, (newVal, oldVal) => { console.log(`state.midObj = ${JSON.stringify(newVal)}, deep=true`) }, { deep: true }) setTimeout(() => { state.midObj.innerObj.size = 1 }, 2000) // 监听多个属性 watch([num, () => totalCount.value], ([numVal, totalVal], [oldNumVal, OldTotalVal]) => { console.log(`num is ${numVal}, count + num = ${totalVal}`) }) // 副作用,会立即执行 let callTimes = 0 const stopEffect = watchEffect(() => { console.log('watchEffect is called!') const div = document.createElement('div') div.textContent = `totalCount is ${totalCount.value}` document.body.appendChild(div) // 调用 5 次后,取消effect监听 callTimes++ if (callTimes >= 5) stopEffect() }) return { ...toRefs(state), num, totalCount, addCount, addNum } } }
2.x中生命周期钩子放在跟methods
同级属性下
3.x中需要先导入钩子,然后在setup
方法中注册钩子回调,并且钩子命名也跟React保持一样了
3.x移除了2.x中的beforeCreate
和created
钩子,通过setup
方法代替
与 React Hooks 相比
基于函数的组合式 API 提供了与 React Hooks 同等级别的逻辑组合能力,但是它们还是有很大不同:组合式 API 的 setup
() 函数只会被调用一次,这意味着使用 Vue 组合式 API 的代码会是:
一般来说更符合惯用的 JavaScript 代码的直觉;
不需要顾虑调用顺序,也可以用在条件语句中; 不会在每次渲染时重复执行,以降低垃圾回收的压力; 不存在内联处理函数导致子组件永远更新的问题,也不需要 useCallback; 不存在忘记记录依赖的问题,也不需要“useEffect”和“useMemo”并传入依赖数组以捕获过时的变量。Vue 的自动依赖跟踪可以确保侦听器和计算值总是准确无误。 我们感谢 React Hooks 的创造性,它也是本提案的主要灵感来源,然而上面提到的一些问题存在于其设计之中,且我们发现 Vue 的响应式模型恰好为解决这些问题提供了一种思路。
javascript 体验AI代码助手复制代码// vue2.x export default { data () { return {} }, methods: { ... }, beforeCreate() {}, created() {}, beforeMount() {}, mounted() {}, beforeUpdate() {}, updated() {}, beforeDestroy() {}, destroyed() {} }
--
javascript 体验AI代码助手复制代码// vue3.x import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue' export default { setup() { onBeforeMount(() => { console.log('component is onBeforeMount') }) onMounted(() => { console.log('component is onMounted') }) onBeforeUpdate(() => { console.log('component is onBeforeUpdate') }) onUpdated(() => { console.log('component is onUpdated') }) onBeforeUnmount(() => { console.log('component is onBeforeUnmount') }) onUnmounted(() => { console.log('component is onUnmounted') }) } }
2.x钩子对比3.x
2.x中,vue template只允许有一个根节点
3.x中,vue template支持多个根节点,用过React的人应该知道
和<></>
xml 体验AI代码助手复制代码// vue2.xhello world
--
xml 体验AI代码助手复制代码// vue3.xhello world
teleport
参照React中的portal
,可以将元素渲染在父节点以外的其他地方,比如下面的某个子元素
在vue3中,
是一个内置标签,我们通常将弹窗、tooltip等元素放在关闭的 标签之前,如下:
xml 体验AI代码助手复制代码
如果按照以往的思路,需要将模态的UI代码放在底部,如下:
xml 体验AI代码助手复制代码Tooltips with Vue 3 Teleport
这样做是因为弹窗、tooltip需要显示在页面上层,需要正确处理父元素定位和z-index上下层级顺序,而最简单的解决方案是将这类DOM放在页面的最底部。这样的话这部分逻辑就脱离了整个项目的跟组件App的管理,就造成直接用JavaScript和CSS来修改UI,不规范并且失去响应式了。为了允许将一些UI片段段移动到页面中的其他位置,在Vue3中添加了一个新的
组件,并且
会在组件销毁时自动清空相应的dom,不用人工处理。
要使用
,首先要在页面上添加一个元素,我们要将模态内容渲染到该元素下面。
代码如下:
xml 体验AI代码助手复制代码Tooltips with Vue 3 Teleport
--
xml 体验AI代码助手复制代码Click to open modal! (With teleport!)...
是一个特殊的组件,它将呈现回退内容,而不是对于的组件,直到满足条件为止,这种情况通常是组件setup
功能中发生的异步操作或者是异步组件中使用。例如这里有一个场景,父组件展示的内容包含异步的子组件,异步的子组件需要一定的时间才可以加载并展示,这时就需要一个组件处理一些占位逻辑或者加载异常逻辑,要用到
,例如:
xml 体验AI代码助手复制代码// vue2.x...Loading...
或者在vue2.x中使用vue-async-manager
xml 体验AI代码助手复制代码...Loading...
--
xml 体验AI代码助手复制代码// vue3.xLoading...
上面代码中,假设
是一个异步组件,直到它完全加载并渲染前都会显示占位内容:Loading,这就是
的简单用法,该特性和Fragment以及 一样,灵感来自React
Vue3.0全家桶最全入门指南 - 快速搭建 (1/4)
Vue3.0全家桶最全入门指南 - vue3.0新特性 (2/4)
Vue3.0全家桶最全入门指南 - vue-router@4.x和vuex@4.x (3/4)
Vue3.0全家桶最全入门指南 -3.x跟2.x的其他差异(4/4)
有话要说...