Понимание новых возможностей Vue3

Vue.js
Понимание новых возможностей Vue3

Приглашаем всех присоединиться к нам, чтобы учиться и развиваться вместе.

Я буду продвигать последние новости и отличные статьи как можно скорее.

Видео объяснение видео станции BWoohoo. Scale Proportion.com/video/BV1…

О времени выхода

Пожалуйста, обратитесь к официальному расписанию, чтобы узнать конкретное время.Официальное расписание

В настоящее время в бета-версии Vue3 последняя предназначена в основном для решения проблем со стабильностью. То есть не будет много улучшений основного API. Ю Дашен сказал в прямом эфире, что хотя идей много, большие изменения появятся в 3.1 как можно скорее. Так что текущая версия должна сильно отличаться от официальной версии. Кажется, что вероятность выхода Q2 очень высока.

карта мозга

>>>>>>>️ Ссылка на карту мозга

Адрес семейного сегмента Vue3

Project Status
vue-router Alpha [Proposed RFCs] [GitHub] [npm]
vuex Alpha, with same API [GitHub] [npm]
vue-class-component Alpha [Github] [npm]
vue-cli Experimental support via vue-cli-plugin-vue-next
eslint-plugin-vue Alpha [Github] [npm]
vue-test-utils Alpha [Github] [npm]
vue-devtools WIP
jsx WIP

Элегантное управление версиями исходного кода

Чтобы иметь возможность ссылаться на исходный код vue3 в тестовом проекте и изящно обновляться в следующем обновлении версии. Я решил использовать функцию подмодуля git для управления

Введение в подмодули gitblog.CSDN.net/Guotianqing…

Инициализировать подмодуль

git submodule add https://github.com/vuejs/vue-next source/vue-next

Содержимое подмодуля записывается в файл .gitmodules

# 更新模块
git submodule init
# 更新模块
git submodule update --init --recursive

Скомпилируйте исходный код

Установить зависимости

## 修改镜像
yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

## 去除pupteer
# 忽略下载Chromium
yarn --ignore-scripts

упаковать и скомпилировать

yarn dev

HelloWorld

Классический API

<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>Document</title>
    <script src="../../source/vue-next/packages/vue/dist/vue.global.js"></script>
</head>

<body>
    <div id='app'></div>
    <script>
        const App = {
            template: `
                <button @click='click'>{{message}}</button>
            `,
            data() {
                return {
                    message: 'Hello Vue 3!!'
                }
            },
            methods: {
                click() {
                    console.log('click ....', this.message)
                    this.message = this.message.split('').reverse().join('')
                }
            }
        }
        let vm = Vue.createApp(App).mount('#app')
        // console.log(vm)
    </script>
</body>

</html>

Составной API

<!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>Document</title>
    <script src="../../source/vue-next/packages/vue/dist/vue.global.js"></script>
</head>

<body>
    <div id="app"></div>
    <script>
        const { createApp, reactive,watchEffect } = Vue
        const App = {
            template: `
                <button @click="click">
                {{ state.message }}
                </button>
            `,
            setup() {
                const state = reactive({
                    message:'Hello Vue 3!!'
                })
                watchEffect(() => {
                    console.log('state change ', state.message)
                })
                function click() {
                    state.message = state.message.split('').reverse().join('')
                }
                return {
                    state,
                    click
                }
            }
        }
        createApp(App).mount('#app')
    </script>
</body>

</html>

Отладка точки останова браузера

Установка зависит от компиляции исходного кода

Вы можете открывать локальные файлы прямо через браузер

Вы можете попробовать эффект щелчка Далее, если вы хотите отладить исходный код, вы обнаружите, что код упакован и не может быть отлажен непосредственно в исходном коде.

Добавить файл SourceMap

Чтобы иметь возможность видеть исходный код в браузере и иметь возможность отладки с точками останова, необходимо добавить исходную карту в упакованный код.

 # 设置环境变量 windows
 set SOURCE_MAP=true && yarn dev
 # 设置环境变量 mac/linux
 export SOURCE_MAP=true && yarn dev

Введение в CompositionApi

Исходный код альфа. В этой статье давайте попробуем самый важный RFC CompositionAPI в версии 3.0.

концепция

CompositionAPI можно перевести в Composition API, хотя бы буквально. Его предыдущее имя было Vue Function-based API, которое, как мне кажется, теперь более выразительно. По сути, CompositionAPI был создан для более удобной реализации комбинации логики.

Оглядываясь назад на историю

Если Vue2 хочет реализовать логическое соответствие в компонентах, например, все компоненты кнопок должны реализовывать защиту от сотрясений, есть примерно три варианта:

  • Mixins
  • Компоненты высшего порядка (также известные как HOC)
  • Компоненты без рендеринга (компоненты, которые инкапсулируют логику на основе слотов с ограниченной областью действия / слотов с ограниченной областью действия).

Но тройка не очень идеальна, главная проблема существует

  • Источник данных шаблона не ясен, например, миксинам трудно определить, откуда берется атрибут, просто взглянув на шаблон.
  • конфликт пространств имен
  • проблемы с производительностью. Например, HOC требует дополнительной вложенности логики компонентов, что приведет к ненужным потерям производительности.

Но что меня больше волнует, так это то, что мне нужно ввести другие концепции, чтобы иметь дело с примитивным программным поведением логических комбинаций. Конечно, именно поэтому многие архитекторы, отвернувшиеся от java, предпочитают реагировать. Vue — это язык, который начинается с улыбки, а заканчивается плачем. Начало работы кажется достаточно простым, чтобы увидеть работу helloworld, но как только вы столкнетесь с проблемой, вам нужно будет представить новую концепцию. Не так чисто и естественно, как функции React как компонентов.

мотивация

первый взгляд на

const App = {
            template: `
                <button @click='click'>{{message}}</button>
            `,
            data() {
                return {
                    message: 'Hello Vue 3!!'
                }
            },
            methods: {
                click() {
                    console.log('click ....', this.message)
                    this.message = this.message.split('').reverse().join('')
                }
            }
        }
        let vm = Vue.createApp().mount(App, '#app')

варианты расположения исходного кода API

Классический Vue API можно охарактеризовать как API опций, который можно понимать как API, который объявляет логику на основе конфигурации. Что это означает, в основном основано на определении. Подумайте о том, может ли helloworld vue2 реализовать эту функцию, выполнив несколько простых определений. Я думаю, что это также причина, по которой vue так популярен: очень просто описать общую логику. Конечно, это также во многом связано с тем, что Ю Дашен был дизайнером. Не позволяйте языкам css и html прямо определять код.

Но если бизнес-логика усложнится, с этим выражением возникнут проблемы. Потому что раз логика усложняется, то ее нельзя писать в кучу, надо продумать, как ее организовать, и надо продумать проблему повторного использования при извлечении общей части логики, иначе программа станет очень трудной для понимания. поддерживать. Какие три метода повторного использования были упомянуты выше, с одной стороны, это должно быть из-за новой концепции, а опыт программирования действительно плохой и есть проблемы с производительностью.

CompositionAPI вдохновлен React Hooks (это особенно признано). Хорошие вещи нужно извлечь из этого, все не должны презирать цепи. Используя API композиции функций, API ассоциации можно извлечь в функцию композиции, которая инкапсулирует связанную логику и возвращает состояние, которое необходимо предоставить компоненту в качестве реактивного источника данных.

реальный бой

Хорошо, приведенный выше код, первая логика — это логика отслеживания положения мыши.

    // 尤大神的经典例子 鼠标位置侦听逻辑 
    function useMouse() {
            const state = reactive({
                x: 0,
                y: 0
            })
            const update = e => {
                state.x = e.pageX
                state.y = e.pageY
            }
            onMounted(() => {
                window.addEventListener('mousemove', update)
            })
            onUnmounted(() => {
                window.removeEventListener('mousemove', update)
            })
            return toRefs(state)
        }

Мы также хотим объединить другую часть логики, например логику времени, которая обновляется в любое время.

function useOtherLogic() {
            const state = reactive({
                time: ''
            })
            onMounted(() => {
                setInterval(() => {
                    state.time = new Date()
                }, 1000)
            })
            return toRefs(state)
        }

В реальной работе мы можем подумать, что эти две логики могут быть повторно использованы многими компонентами.Подумайте о том, сколько вы потеряете, если будете использовать mixin и hoc. Но с CompositionAPI нам просто нужно объединить его и выставить вот так

       const MyComponent = {
            template: `<div>x:{{ x }} y:{{ y }} z:{{ time }} </div>`,

            setup() {
                const {
                    x,
                    y
                } = useMouse()
                // 与其它函数配合使用
                const {
                    time
                } = useOtherLogic()

                return {
                    x,
                    y,
                    time
                }
            }
        }
        createApp().mount(MyComponent, '#app')

Э-э-э, похоже, это реактивные хуки, неважно, ими легко пользоваться. . .Полный пример приветственной звезды

Полное введение в API

Давайте посмотрим, каковы основные API Vue3?

const {
            createApp,
            reactive, // 创建响应式数据对象
            ref, // 创建一个响应式的数据对象
            toRefs, // 将响应式数据对象转换为单一响应式对象
            isRef, // 判断某值是否是引用类型
            computed, // 创建计算属性
            watch, // 创建watch监听
            // 生命周期钩子
            onMounted,
            onUpdated,
            onUnmounted,
        } = Vue

установка использует запись API композиции

Функция настройки будет выполнена после beforeCreate и до создания

setup(props,context){
    console.log('setup....',)
    console.log('props',props) // 组件参数
    console.log('context',context) // 上下文对象
} 

Ну, на самом деле, каждый может сидеть со своим исходным кодом React и Vue2.

reactive

Функция reactive() принимает обычный объект и возвращает реактивный объект данных.

    const state = reactive({
        count: 0,
        plusOne: computed(() => state.count + 1)
    })

ссылка и isRef

  • ref создает реактивный объект данных с заданным значением (в частности, базовый тип данных ini или строка)
  • isRef на самом деле должен определить, является ли он адаптивным объектом данных, сгенерированным ref

Прежде всего, существует важная концепция, называемая оберткой значений.Дети, которые говорят об обертках из java, должны помнить, что концепция wrapclass в java почти такая же. Мы знаем, что базовые типы данных имеют только значения и не имеют ссылок, что также вызывает проблему.Возврат базового типа данных, такого как строка, не может отслеживать его состояние, поэтому нам нужно обернуть базовый тип данных, который является немного похоже на ReactHooks.useRef, но объект, обернутый во Vue, сам по себе является чувствительным источником данных. Что ж, давайте посмотрим на пример, чтобы понять

    // 定义创建响应式数据
    const time = ref(new Date())
    // 设置定时器为了测试数据响应
    setInterval(() => time.value = new Date(), 1000)

    // 判断某值是否是响应式类型
    console.log('time is ref:', isRef(time))
    console.log('time', time)
    console.log('time.value', time.value)
    
    // 我们看看模板里面我们这样展示
    template: `
        <div>
            <div>Date is {{ time }}</div>
        </div>
    `

toRefs

  • toRefs может расширять объект, созданный реактивным, до базового типа
    // 如果不用toRefs
    const state = reactive({
        count: 0,
        plusOne: computed(() => state.count + 1)
    })
    return {
        state
    }
    // 模板渲染要这样写
    template: `
    <div>
        <div>count is {{ state.count }} </div>
        <div>plusOne is {{ state.plusOne }}</div>
    </div>
    `
    
    // 我们再看看用了toRefs
    const state = reactive({
        count: 0,
        plusOne: computed(() => state.count + 1)
    })
    return {
        ...toRefs(state)
    }
    // 模板渲染要这样写
    template: `
    <div>
        <div>count is {{ count }} </div>
        <div>plusOne is {{ plusOne }}</div>
    </div>
    `

часы определяют слушателя

В этом действительно нет ничего нового

   watch(() => state.count * 2, val => {
        console.log(`count * 2 is ${val}`)
    })

эффект побочный эффект функция

Реактивная модификация объекта вызовет эту функцию

    // 副作用函数
    effect(() => {
        console.log('数值被修改了..',state.count)
    })

вычисляемые свойства

const state = reactive({
    count: 0,
    plusOne: computed(() => state.count + 1)
})

Крючки жизненного цикла

Vue3 Vue3
beforeCreate установка (альтернативный вариант)
created установка (альтернативный вариант)
beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeDestroy onBeforeUnmount
destroyed onUnmounted
errorCaptured onErrorCaptured

Полная реализация кода

Глубокое понимание исходного кода через Jest

Теперь готовы перейти к основному исходному коду. Сначала нужно решить небольшую проблему. Это изучение того, как запускать модульные тесты Vue3. В конце концов, читать код, не запуская его, — бездушно. Спросите китайских фанатов, смотрят ли они, а не играют.

Код Vue3 тестируется на основе Jest, давайте кратко рассмотрим, что такое jest.

Введение в шутку

Jest — это инструмент Facebook для модульного тестирования Javascript, подходящий для JS и NodeJS, с нулевой конфигурацией, встроенным покрытием кода и мощными моками.

Короче говоря, Jest в мире JS — относительно систематический инструмент тестирования. Почему вы так говорите?Например, сравните его с предыдущим тестовым фреймворком Mocha.Это всего лишь тестовый фреймворк.Если вам нужны утверждения, вам нужна специальная библиотека утверждений, например, должны ожидаться утверждения и т. д. Если вам нужен Mock, вы нужно жить в нашей библиотеке, чтобы его поддерживать, что очень неудобно. Но Jest может сделать все сразу.

Соглашения об именах файлов каталогов

Тестовый код Jest и код логики следуют соглашению по конфигурации (соглашение по конфигурации) На самом деле, это также общепринятый принцип в мире программирования.

Тестовый код Jest основан на следующих соглашениях.

  • Имя тестового файла должно начинаться с результата спецификации.
  • Суффикс тестового файла — js, jsx, ts, tsx
  • Файлы тестов необходимо поместить в каталог test/unit/ или /tests/Под содержанием

Пока тестовый файл соответствует этим трем требованиям, он будет автоматически выполняться при запуске с ним jest.

По сути, это правило похоже на соглашение Maven для тестового кода и логического кода, за исключением того, что тестовый каталог заменяется на __tests__

Давайте подробно рассмотрим структуру каталогов исходного кода Vue3.

На самом деле очень удобно размещать код логики и код теста соответственно. Давайте посмотрим на другой реактивный пакет

Запустите полные тесты

jest уже настроен в файле package.json

npm run test

покрытие

Мы добавляем параметр для запуска покрытия

npx jest --coverage 

На самом деле это ошибка запускать репортаж, оставим его в покое Давайте сначала проанализируем, как выглядит этот отчет.Если вы изучали программную инженерию, вы знаете, что теоретически показатель охвата включает

  • покрытие заявлений
  • Покрытие узла
  • охват пути
  • Покрытие комбинации условий

Но вообще говоря, у разных фреймворков разное понимание, в Jest оно, наверное, разбито так

  • %stmts — это покрытие операторов: выполняется ли каждый оператор?
  • Покрытие ветвей %Branch: выполняется ли каждый блок if?
  • Покрытие функции %Funcs (покрытие функции): вызывается ли каждая функция?
  • Покрытие строк %Lines: выполняется ли каждая строка?

провести тест в одиночку

Например, давайте посмотрим на индексный тест vue.

Есть два способа индивидуального тестирования

// 全局安装
npm i jest -g
jest index

// 或者更更简便一点
npx jest index

index.spec.ts

import { createApp } from '../src'

it('should support on-the-fly template compilation', () => {
  const container = document.createElement('div')
  const App = {
    template: `{{ count }}`,
    data() {
      return {
        count: 0
      }
    }
  }
  createApp().mount(App, container)
  // 断言 
  expect(container.innerHTML).toBe(`0`)
})

В заявлении говорится, что для подтверждения того, что компиляция шаблона может вступить в силу, это простая привязка данных Наконец, утверждение также проверяет, равно ли значение count 0. На самом деле, за исключением части утверждения, пример можно напрямую скопировать в html-файл, упомянутый впервые.

Реактивное модульное тестирование

Взгляните на тестовый код, соответствующий каждому пакету, помещенному в файл __tests__.

    npx jest reactive --coverage

Что ж, мы можем перейти к исходному коду позже.

структура кода

Расположение исходного кода находится в файле пакета.На самом деле исходный код в основном разделен на две части: компилятор и среда выполнения.

  • переводчик

    • основная логика компиляции ядра компилятора

      • Разрешение базового типа
      • AST
    • компилятор-дом Логика компилятора для браузеров

      • v-html
      • v-text
      • v-model
      • v-clock
  • среда выполнения

    • ядро выполнения
      • inject
      • жизненный цикл
      • watch
      • directive
      • component
    • специфическая для браузера логика среды выполнения runtime-dom
      • class
      • style
    • моделирование тестовой среды во время выполнения

      В основном для решения логики задачи модульного тестирования удобнее выполнять тест вне браузера.

  • логика реагирования на реактивность

  • синтаксический анализатор шаблонов можно запустить так

    yarn dev template-explorer
    

    Затем откройте index.html

  • ввод кода vue

    Интеграция компилятора и среды выполнения

  • server-renderer Рендеринг на стороне сервера (TODO)

  • поделиться общедоступным методом

Сравнение методов ответа Vue2 и Vue3

Что такое отзывчивый Vue2

Сначала поговорим о том, что является отзывчивым. С помощью определенного метода данные могут быть изменены, и соответствующий ответ может быть свободно определен, что называется отзывчивым.

В частности, потребность в ViewModel в нашем MVVM заключается в том, что данные изменяются, и представление должно реагировать. Если вы используете прецедент Jest, это похоже на это

    it('测试数据改变时 是否被响应', () => {
        const data = reactive({
            name: 'abc',
            age: {
                n: 5
            }
        })
        // Mock一个响应函数
        const fn = jest.fn()
        const result = fn()

        // 设置响应函数
        effect(fn)

        // 改变数据
        data.name = 'efg'

        // 确认fn生效
        expect(fn).toBeCalled()
    })

Предположим, что нам нужно, чтобы функция fn могла запускаться при изменении данных, то есть соответствующий ответ.Конечно, ответ, как правило, вызывает обновление представления, конечно, может и не быть. Здесь мы используем jest, чтобы создать фиктивную функцию, чтобы определить, нужно ли делать ответ.

Окончательный код expect(fn).toBeCalled() валиден, что означает, что тест пройден, то есть сделан соответствующий ответ.

Решение Vue2

Ниже показана реализация vue2 путем переопределения методов получения и установки с помощью Object.defineProperty.

let effective
function effect(fun) {
    effective = fun
}

function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }


    Object.keys(data).forEach(function (key) {
        let value = data[key]
        Object.defineProperty(data, key, {
            emumerable: false,
            configurable: true,
            get: () => {
                return value
            },
            set: newVal => {
                if (newVal !== value) {
                    effective()
                    value = newVal
                }
            }
        })

    })
    return data
}

module.exports = {
    effect, reactive
}

Конечно, есть еще две важные проблемы, которые необходимо решить.Первая заключается в том, что это может дать только поверхностный ответ, то есть, если это второй слой, он не будет работать.

it('测试多层数据中改变时 是否被响应', () => {
        const data = reactive({
            age: {
                n: 5
            }
        })
        // Mock一个响应函数
        const fn = jest.fn()

        // 设置响应函数
        effect(fn)

        // 改变多层数据
        data.age.n = 1

        // 确认fn生效
        expect(fn).toBeCalled()
    })

Например, следующий вариант использования не будет работать Конечно, есть решения для рекурсивных вызовов.

Конечно, рекурсия также приносит огромные потери в производительности, запомните это в первую очередь.

Затем возникает проблема массива.Проблема массива может быть решена перехватом функций.

const oldArrayPrototype = Array.prototype
const proto = Object.create(oldArrayPrototype);

['push','pop','shift','unshift','splice','sort','reverse'].forEach(method => {
    
    // 函数劫持
    proto[method] = function(){
        effective()
        oldArrayPrototype[method].call(this,...arguments)
    }
})
// 数组通过数据劫持提供响应式
if(Array.isArray(data)){
    data.__proto__ = proto
}

Vue3

В новой версии Vue3 для решения этой проблемы используется прокси-метод ES6. Две проблемы, с которыми я столкнулся раньше, намного проще. Во-первых, Proxy поддерживает массивы, то есть массивы не требуют специального кода. Нет необходимости использовать рекурсивное решение для глубокого мониторинга. Когда get является оценочным значением объекта, достаточно выполнить реактивную обработку и вернуть объект. Все думают, что этого не происходит в момент инициализации, но когда стоит настройка, конечно, производительность сильно повышается.

function reactive(data) {
    if (typeof data !== 'object' || data === null) {
        return data
    }
    const observed = new Proxy(data, {
        get(target, key, receiver) {
            // Reflect有返回值不报错
            let result = Reflect.get(target, key, receiver)

            // 多层代理
            return typeof result !== 'object' ? result : reactive(result) 
        },
        set(target, key, value, receiver) {
            effective()
            // proxy + reflect
            const ret = Reflect.set(target, key, value, receiver)
            return ret
        },

        deleteProperty(target,key){
            const ret = Reflect.deleteProperty(target,key)
            return ret
        }

    })
    return observed
}

Конечно, есть еще преимущества и недостатки, например, проблемы совместимости в настоящее время не поддерживают Proxy в IE11. Однако я считаю, что полная поддержка ES6 — это необратимая тенденция, которой нет дела.

Чтобы сравнить и понять разницу между адаптивными реализациями Vue2 и 3, я написал обе реализации и сопоставил их с помощью шутливых тестов. Вы можете обратиться кGitHub.com/su37Джозеф Х…

// clone代码
yarn
npx jest reactivity-demo

пользовательский рендерер

Этот пользовательский рендерер очень похож на React’s Render. Различные рендереры могут быть определены по мере необходимости

Конкретный контент недавно обновлен

новый инструмент

Мы знаем, что импорт полностью доступен в браузерах с синтаксисом ES6. Может использоваться для загрузки внутренних ресурсов, но мы проигнорировали эту функцию. Вероятно, из-за того, что webpack делает это слишком хорошо. Мы игнорировали его существование. Vite использует эту функцию, но делает еще один шаг вперед и обеспечивает поддержку файлов vue. Хотя это может быть немного резко.

  • Простой HTTP-сервер
  • Нет необходимости в веб-пакете
  • Рендеринг файла Vue напрямую
  • Горячее обновление