13-Vue3初体验

478 阅读4分钟

一、调试环境搭建

  • 拉取Vue3源码
git clone https://github.com/vuejs/vue-next.git
  • 安装依赖
yarn

使用yarn安装,npm安装依赖后,运行会出问题

  • 添加SourceMap文件

【rollup.config.js】

// 76行添加如下代码 
output.sourcemap = true

【tsconfig.json】

"sourceMap": true
  • 编译
yarn dev

生成:packages/vue/dist/vue.global.js

  • 测试代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hello vue3</title>
    <script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
    <div id='app'></div>
    <script>
        const App = {
            template: `<h1>{{message}}</h1>`,
            data: { message: 'Hello Vue 3!' }
}
        Vue.createApp(App).mount('#app')
    </script>
</body>
</html>

二、源码结构

源码的位置在package文件内,实际上源码主要分为两部分:编译器和运行时环境

编译器

  • compiler-core 核心编译逻辑
    • 模板解析
    • AST
    • 代码生成
  • compiler-dom 针对浏览器的编译逻辑
    • v-html
    • v-text
    • v-model

运行时环境

  • runtime-core 运行时核心
    • inject
    • 生命周期
    • watch
    • directive
    • component
  • runtime-dom 运行时针对浏览器的逻辑
    • class
    • style
  • runtime-test 测试环境仿真,主要为了解决单元测试问题的逻辑 在浏览器外完成测试比较方便

reactivity 响应式逻辑

template-explorer 模板解析器 可以这样运行

yarn dev template-explorer

vue 代码入口,整合编译器和运行时

server-renderer 服务器端渲染

share 公用方法

三、Composition API

Composition API字面意思是组合API,它是为了更方便的实现逻辑的组合而产生的。

主要API

const {
    createApp,
    reactive, // 创建响应式数据对象
    ref, // 将单个值包装为一个响应式对象
    toRefs, // 将响应式数据对象转换为单一响应式对象 computed, // 创建计算属性
    watch, // 创建watch监听
    // 生命周期钩子
    onMounted,
    onUpdated,
    onUnmounted,
} = Vue

基本使用

setup函数

setup函数会在 beforeCreate之后 created之前执行

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>hello vue3</title>
    <script src="../packages/vue/dist/vue.global.js"></script>
</head>
<body>
    <div id='app'></div>
    <script>
        const {createApp,reactive}=Vue
        const App = {
            template: `<h1>{{state.message}}</h1>`,
            // data: { message: 'Hello Vue 3!' },
            // setup函数会在 beforeCreate之后 created之前执行
            setup(){
                // 使用reactive做像樱花处理
                const state=reactive({message:'Hello Vue 3!'})
                return {state}
            }
        }
        createApp(App).mount('#app')
    </script>
</body>
</html>

事件处理

const {createApp,reactive}=Vue
const App = {
    template: `<h1 @click="onClick">{{state.message}}</h1>`,
    // data: { message: 'Hello Vue 3!' },
    // setup函数会在 beforeCreate之后 created之前执行
    setup(){
        // 使用reactive做像樱花处理
        const state=reactive({message:'Hello Vue 3!'})
        function onClick(){
            state.message='Vue3初体验!'
        }
        
        return {state,onClick}
    }
}
createApp(App).mount('#app')

生命周期

const { onMounted } = Vue
const App = {
    setup() {
        onMounted(() => { console.log('App挂载!')}) 
    }
}

单值响应化:ref

const { ref } = Vue
const App = {
    template: `<h1 @click="onClick">{{ message }}</h1>`,
    setup() {
        // ref返回包装对象
        const message = ref('Hello Vue 3!'); 
        function onClick() {
            // 包装对象要修改其value
            message.value = 'Vue3初体验!';
        }
        // 指定包装对象到上下文,模板中可以直接用
        return { message, onClick };
    }
}

toRefs:将reactive创建出的对象展开为基础类型

const { toRefs } = Vue
const App = {
    template: `<h1 @click="onClick">{{message}} | {{foo}} | {{bar}}</h1>`,
    setup() {
        // 使用reactive做像樱花处理
        const state=reactive({
            message:'hello vue3!'
            bar:'bar',
            foo:'foo'
        })
        function onClick(){
            state.message='Vue3初体验!'
        }
        // 转换每个属性并展开
        return { ...toRefs(state), onClick };
    } 
    
}

toRefs转过之后,原先页面中使用是state.message,转为基础类型后,使用变为message

computed:计算属性

const {createApp,reactive,ref,toRefs,onMounted,computed}=Vue
const App = {
    template: `<h1 @click="onClick"> {{foo}} | {{bar}} | {{msg}}</h1>`,
    // setup函数会在 beforeCreate之后 created之前执行
    setup(){
        // 使用reactive做像樱花处理
        const state=reactive({
            bar:'bar',
            foo:'Hello Vue 3!',
            msg:computed(()=>'computed'+state.bar)
        })
        return {...toRefs(state),onClick}
    }
}

watch:创建监控表达式

const {createApp,reactive,ref,toRefs,onMounted,computed,watch}=Vue
const App = {
    template: `<h1 @click="onClick">{{bar}}</h1>`,
    // data: { message: 'Hello Vue 3!' },
    // setup函数会在 beforeCreate之后 created之前执行
    setup(){
        // 使用reactive做像樱花处理
        const state=reactive({
            bar:'bar',
            foo:'Hello Vue 3!',
            msg:computed(()=>'computed'+state.bar)
        })
        function onClick(){
            state.bar='更改bar'
        }
        watch(()=>state.bar,(val,oldVal)=>{
            console.log(`bar变了,新值为:${val}`);
        })
        return {...toRefs(state),onClick}
    }
}

四、数据响应式革新

vue2响应式问题

  • data/computed/props中数据规模庞大,遍历起来会很慢,要监听所有属性的变化,占用内存会很大
  • 无法监听Set/Map的变化;Class类型的数据无法监听;属性新加或删除无法监听;数组元素 增加和删除无法监听;对于数组需要额外实现方法拦截,对应的修改语法也有限制。

vue3响应式原理:使用ES6的Proxy来解决这些问题。

function reactives(data) {
    if (typeof data !== 'object' || data === null) {return data }
        // Proxy相当于在对象外层加拦截
        // http://es6.ruanyifeng.com/#docs/proxy 
        const observed = new Proxy(data, {
        // 获取拦截
        get(target, key, receiver) {
            // Reflect用于执行对象默认操作,Proxy的方法它都有对应方法 
            // Reflect更规范、更友好
            // http://es6.ruanyifeng.com/#docs/reflect
            console.log(key+':获取');
            const val = Reflect.get(target, key, receiver); // 若val为对象则定义代理
            return typeof val === 'object' ? reactives(val) : val;
        },
        // 新增、更新拦截
        set(target, key, value, receiver) {
            console.log(key+':修改');
            return Reflect.set(target, key, value, receiver)
        },
        // 删除属性拦截 
        deleteProperty(target, key){
            console.log(key+':删除');
            return Reflect.deleteProperty(target,key)
        }
    })
    return observed
}

测试代码:

const data = {
    foo: 'foo',
    obj: {a:1},
    arr: [1,2,3]
}
const react = reactives(data)

// get
react.foo// foo:获取
react.obj.a // obj:获取  a:获取
react.arr[0] // arr:获取 0:获取
// set
react.foo = 'fooooo' // foo:修改
react.obj.a = 10 // obj:获取 a:修改
react.arr[0] = 100 // arr:获取 0:修改
// add
react.bar = 'bar'// bar:修改
react.obj.b = 10 // obj:获取 b:修改
react.arr.push(4) // arr:获取 push:获取 length:获取 3:修改 length:修改
react.arr[4] = '5' // arr:获取 4:修改
// delete
delete react.bar // bar:删除
delete react.obj.b // obj:获取 b:删除 
react.arr.splice(4, 1) 
// arr:获取 splice:获取 length:获取 constructor:获取 4:获取 4:删除 length:修改
delete react.arr[3] // arr:获取 3:删除

Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。

Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。

var obj = new Proxy({}, {
  get: function (target, propKey, receiver) {
    console.log(`getting ${propKey}!`);
    return Reflect.get(target, propKey, receiver);
  },
  set: function (target, propKey, value, receiver) {
    console.log(`setting ${propKey}!`);
    return Reflect.set(target, propKey, value, receiver);
  }
});

五、vue3展望

  • vue3会兼容之前写法,仅少量Portal、Suspense之类的组件需要看看,composition可选
  • 目前为止,相关工具、生态、库都跟不上
  • vue3重要特性如响应式会给大数据量应用带来福音。time-slicing对于用户交互敏感的 应用是重要特性,比如证券类应用;号称性能很好,移动端应用也更加适合使用了。
  • 虽然不是正式版,但仍可提前学习vue3设计和实现思想

六、参考

阮一峰ES6——proxy