Опыт и разработка Vue3+TS +анализ Vite

Vue.js

предисловие

Vue3Это было давно, и я учился, но домовладелец - студент, и я недавно делал некоторые апплеты WeChat и визуализацию, поэтому обновление немного медленное. Пожалуйста, поймите.

В этой статье в основном рассказывается о моем собственном понимании и использовании в процессе изучения Vue3+Ts+vite.

Ts

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

Если вы хотите узнать больше. Рекомендовать следующие книги

порекомендовать книгуНачало работы с TypeScript, это помещается вGithub(Может потребоваться научный доступ в Интернет). (Рекомендуется сначала прочитать эту книгу, так как лично я считаю, что ее относительно легко понять.)

Вторая книгаОфициальное руководство(Может также потребоваться научный доступ в Интернет), по сравнению с первым, я думаю, что это сложнее понять, поэтому я рекомендую первый

Базовая среда и синтаксис Vue3

Настроить среду разработки vue3

// 安装或者升级
npm install -g @vue/cli
yarn global add @vue/cli


// 全局升级vueCli
npm update -g @vue/cli
// 或者
yarn global upgrade --latest @vue/cli



// 保证 vue cli 版本在 4.5.0 以上
vue --version

// 创建项目
vue create my-project

// 或者使用图形化界面创建
vue ui

Рекомендации по препроцессору CSS

Рекомендуемое использованиеdart-sass,(глубоко отравлен нодой-дерзостью). Это также официально рекомендуется

Действия в командной строке. В основном некоторые конфигурации, вы можете обратиться к некоторым из вашего обычного использования.Лично рекомендую создание пользовательского интерфейса Vue. Может быть, это хорошо

изменения шаблона

существуетVue2, каждыйtemplateУ узла может быть только один корневой узел.

пока вVue3, может быть несколько корневых узлов

Например

//vue2
<template>
    <div>
        
    </div>
</template>


// vue3
<template>
  <div>
    
  </div>
  <router-view/>
</template>

Добавлен синтаксис

setUp

Это заменяет оригиналdata函数, который может принимать два параметра,props,context(Вы можете опустить его, если не используете его).и выполнить только один раз

образец кода

<template>
  <div>
    <h1>
      num:{{ num }}
    </h1>
  </div>
  <button @click="add">加1</button>
</template>

<script lang="ts">
export default {
  name: 'App',
  setup () {
    let num = 0
    const add = () => {
      num++
    }
    console.log(num)
    return {
      add, num
    }
  }
}
</script>

Это основнойsetupпроцесс, вы можете не увидеть его здесь, иVue2Каковы конкретные различия, не волнуйтесь. попробуй нажатьbuttonкнопка, открытьnumНичего не изменилось. Взгляните на консоль (F12) и обнаружите, что выводnumberценность . Но ничего не изменилось, когда мы щелкнули. Итак, далее мы будем использовать первыйНовый API Ref

Ref

Во-первых, давайте изменим приведенный выше код

<template>
  <div>
    <h1>
      msg:{{ msg }}
    </h1>
    <h1>
      num:{{ num }}
    </h1>
  </div>
  <button @click="add">加1</button>
</template>

<script lang="ts">
import { ref } from 'vue'

export default {
  name: 'App',
  setup () {
    const msg = ref(0)
    let num = 0
    const add = () => {
      msg.value++
      num++
    }
    console.log(msg)
    console.log(num)
    return {
      msg, add, num
    }
  }
}
</script>

Прежде всего, посмотрите на справочную форму, вы можете видеть, что здесьИмпорт по запросу, не буду описывать преимущества по одному, давайте посмотрим непосредственно на консоль

UTOOLS1602765640622.png

одинRefImplодин数值 0. Нажмите кнопку, чтобы узнать,msgможно изменить, при этомnumнет.

RefImplЧто тогда? Я понимаю это какпрокси-объект. как мы знаемVue2серединаdataданные проходят черезObject.defineProperty()перехватить. чтобы достичьданные реагируютцель. а такжеVue3используетсяES6серединаproxy, в сравнении сObject.defineProperty()Другими словами, способов перехвата больше. Функция также более мощная.

Итак, все знают, почемуmsgможно изменить,numНе можете изменить это? потому чтоmsgодеялоproxyпрокси. а такжеnumнисколько. Отсюда описанная выше ситуация

Объясните ссылку

ref это функция, которая принимаетпараметр, который возвращаетреактивный объект.

В примере мы инициализируем это0Оно заключено в этот объект как параметр, и когда значение будет изменено в будущем, изменение может быть обнаружено, и может быть сделан соответствующий ответ.

Что если я хочу создать объект? Эта функция не может быть использована в настоящее время. использовать следующую функциюReactive

Reactive

СоздаватьОбъект JavaScriptРеактивное состояние, необходимо использоватьreactiveметод:

образец кода

<template>
  <div>
    {{ data }}
    <h1>
      count:{{ data.count }}
    </h1>
    <h1>
      double:{{ data.double }}
    </h1>
  </div>
  <button @click="data.increase">加1</button>
</template>

<script lang="ts">
import { reactive, computed } from 'vue'

export default {
  name: 'App',
  setup () {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    return {
      data
    }
  }
}
</script>

В это время мы можем изменить состояние, нажав кнопку.

вычисляемые свойства,ДатьVue2почти. научилсяVue2ученики могут понять. Если вы не понимаете, см. специальное введение в последующем

В настоящее время некоторые люди могут подумать, что это более хлопотно, почему бы не напрямую{{count}}как насчет этого. просто подумалEs6серединаразрушать

Таким образом, код изменяется на следующий

<template>
  <div>
    <h1>
      count:{{ count }}
    </h1>
    <h1>
      double:{{ double }}
    </h1>
  </div>
  <button @click="increase">加1</button>
</template>

<script lang="ts">
import { reactive, computed } from 'vue'

export default {
  name: 'App',
  setup () {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    return {
      ...data
    }
  }
}
</script>

В это время вы найдете проблему.Почему нажатие кнопки не меняет состояние?

Это связано с тем, что деструктуризация уничтожает прокси, программируя его на простое значение. так же, как вышеRefтак же, как в примере, поэтому нажатие на кнопку не меняется.

В это время необходимо пригласить еще одного вновь добавленногоapiсейчас,toRefs

toRefs

Это цитата с официального сайта

toRefsЭта функция полезна при возврате реактивного объекта из функции композиции, так что потребляющий компонент может деконструировать/расширить возвращенный объект безне теряет реактивности:

Он относительно прост в использовании, просто добавьте его, когда вернетесь

<template>
  <div>
    <h1>
      count:{{ count }}
    </h1>
    <h1>
      double:{{ double }}
    </h1>
  </div>
  <button @click="increase">加1</button>
</template>

<script lang="ts">
import { reactive, computed, toRefs } from 'vue'

export default {
  name: 'App',
  setup () {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    return {
      ...toRefs(data)
    }
  }
}
</script>

изменения жизненного цикла

beforeMount onBeforeMount
mounted onMounted
beforeUpdate onBeforeUpdate
updated onUpdated
beforeUnmount onBeforeUnmount
unmounted onUnmounted
errorCaptured onErrorCaptured
renderTracked onRenderTracked
renderTriggered onRenderTriggered

намекать(с официального сайта)

из-заsetupвокругbeforeCreateа такжеcreatedПерехватчики жизненного цикла запускаются, поэтому вам не нужно определять их явно. Другими словами, любой код, который будет написан в этих хуках, должен быть написан непосредственно вsetupнаписано в функции.

оригинальная грамматика

watch

// watch 基本使用   记得引入
watch(data, () => {
  document.title = '更新过后 ' + data.count
})
// watch 的两个参数,代表新的值和旧的值
watch(data, (newValue, oldValue) => {
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后 ' + data.count
})

// watch 多个值,返回的也是多个值的数组
watch([greetings, data], (newValue, oldValue) => {
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后' + greetings.value + data.count
})

// 使用函数 getter写法
watch([greetings, () => data.count], (newValue, oldValue) => {
  console.log('old', oldValue)
  console.log('new', newValue)
  document.title = '更新过后' + greetings.value + data.count
})

обращать внимание,dataявляется объектом. взять значение внутри

computed

как использовать иvue2Такой же. можно написать вreactiveВнутренний, также может быть записан снаружи

setup () {
    const data = reactive({
      count: 0,
      increase: () => {
        data.count++
      },
      double: computed(() => data.count * 2)
    })
    const conComputed = computed(() => data.count * 2)
    const number = ref(0)
    watch(data, () => {
      console.log(data)
      document.title = 'updated ' + data.count
    })
    watch(number, () => {
      console.log(number)
    })
    return {
      number,
      conComputed,
      ...toRefs(data)
    }
  }

Добавить ярлык

Телепорт телепортация

Телепортировать английский адрес документа

Обычно наш маскирующий слой существует под многоуровневой меткой, что на самом деле неразумно.

TeleportВнешний вид позволяет перемещать компонент, который мы пишем, ниже указанной метки.

toпод какой ярлык перейти.Селектор поддержки

образец кода

// model
<template>
  <teleport to="#modal">
    <div id="center">
      <h1>this is a modal</h1>
    </div>
  </teleport>
</template>

<script>
export default {
  name: 'model'
}
</script>

<style scoped lang="scss">
#center {
  width: 200px;
  height: 200px;
  background: red;
  position: fixed;
  left: 50%;
  top: 50%;
  margin-left: -100px;
  margin-top: -100px;
}
</style>


// App.vue
<template>
  <model v-if="show"></model>
  <button @click="show = !show">show</button>
</template>

<script lang="ts">
import { ref } from 'vue'
import model from './components/model.vue'
export default {
  name: 'App',
  components: {
    model
  },
  setup () {
    const show = ref(false)
    return {
      show,
    }
  }
}
</script>

после того, как мы нажмем кнопку

Это основное использование.

Приостановить асинхронный запрос

Эта новая вкладка, на мой взгляд, самая полезная. Решен пробел, вызванный медленным возвратом изображения в нашем асинхронном запросе (хотя это решаемо. Но это не удобно)

образец кода

// showPic
<template>
  <img :src="result && result.url" alt="">
</template>

<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
  async setup () {
    const rawData = await axios.get('https://picsum.photos/id/786/200/200')
    console.log(rawData)
    return {
      result: rawData.config
    }
  }
})
</script>
// App
<template>
  <Suspense>
    <template #default>
      <pic-show />
    </template>
    <template #fallback>
      <h1>Loading !...</h1>
    </template>
  </Suspense>
</template>

<script lang="ts">
import ShowPic from './components/ShowPic.vue'
export default {
  name: 'App',
  components: {
    ShowPic
  }
}
</script>

Эффект следующий

Пользовательские крючки

В этом разделе показаноЧасть об извлечении общего логического кода, просто пример. Просто чтобы показать, как отстраниться.

//useMousePosition.ts
import {onMounted, onUnmounted, ref} from "vue";

function useMousePosition() {
    const x = ref(0)
    const y = ref(0)
    const updateMouse = (e: MouseEvent) => {
        x.value = e.pageX
        y.value = e.pageY
    }

    onMounted(() => {
        document.addEventListener('mousemove', updateMouse)
    })

    onUnmounted(() => {
        document.removeEventListener('mousemove', updateMouse)
    })
    return {x, y }
}

export default useMousePosition

// app
import useMousePosition from "@/hooks/useMousePosition";
export default {
    setup(){
        const {x, y} = useMousePosition()
        return {
            x,y
        }
    }
}

Объединение T для разработки компонентов

Добавлен синтаксис

defineComponent. Этот пакет необходим для создания компонентов.

PropTypeУтверждение типа Определите, какой это тип (оно уже существует в Vue2)

/**
	定义
*/
// 主要写一下TS  template 和 style 就先不写了  这里展示一个基本的下来框
<script lang="ts">
import {
  defineComponent,
  PropType,
  computed
} from 'vue'

// 把接口类型导出。在使用的过程中导入接口,对接口进行定义
export interface ColumnProps {
  id: number;
  title: string;
  avatar?: string;
  des: string;
}

export default defineComponent({
  name: 'ColumnList',
  props: {
    list: {
      type: Array as PropType<ColumnProps[]>,
      required: true
    }
  },
  setup(props) { //  这里使用到了props
    const ColumnList = computed(() => {
      return props.list.map((item) => {
        if (!item.avatar) {
          item.avatar = require('@/assets/logo.png')
        }
        return item
      })
    })
    return {
      ColumnList
    }
  }
})
</script>


// 使用
<template>
  <div id="container">
    <ColumnList :list="list"></ColumnList>
  </div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import ColumnList, { ColumnProps } from '@/components/ColumnList.vue'

const testDate: ColumnProps[] = [
  {
    id: 1,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
    id: 2,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
    id: 3,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }, {
    id: 4,
    title: 'test1',
    des: 'test1',
    avatar: 'https://picsum.photos/id/239/200/200'
  }
]
export default defineComponent({
  name: 'App',
  components: {
    ColumnList,
  },
  setup() {
    return {
      list: testDate,
    }
  }
})
</script>

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

Vite

Что такое Вите

гитхаб:github.com/vitejs/vite

Вите является уроженцемESMМощные инструменты для веб-разработки. Нативный браузер в среде разработкиES importsразработки, в производстве на основеRollup Пакет.

Оригинальные слова автора: Vite, сервер разработки, основанный на встроенных в браузер модулях ES. Используйте браузер для парсинга модуля, компилируйте и возвращайте его на стороне сервера по мере необходимости, полностью отказывайтесь от концепции упаковки, и сервер можно использовать в любое время. При этом он не только поддерживает файлы Vue, но и обрабатывает горячие обновления, причем скорость горячих обновлений не будет замедляться по мере увеличения количества модулей.

В основном он имеет следующие характеристики:

  • быстрый холодный пуск
  • Мгновенное горячее обновление модуля
  • Настоящая компиляция по запросу

Простота установки и использования

// npm i vite

npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

директория с файлами после установки

Открыть страницу как

Что касается интеграцииvue-routerЖдать. Все, пожалуйста, узнайте сами. Вот лишь краткое введение

принцип

Vite Реализация основана на встроенной поддержке браузера.Функция модуля

когдаscript.typeдляmoduleкогда черезsrcа такжеimportИмпортированный файл будет отправленhttpпросить.

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

Простая реализация

Первый шаг, начальная сборка

Сначала я создаю базовую структуру, в которойКорневая директориясоздать следующийviteText.jsдокумент. на основеKoaреализация. Если вы не понимаете. Пожалуйста, посетитеофициальный сайт Коа

const fs = require('fs')
const path = require('path')
const Koa = require('koa')


const app = new Koa()

// 这个主要是对请求进行重定向使用的   可以先不管
function rewriteImport(content) {
// 目的是改造.js文件内容, 不是/ ./ ../开头的import,替换成/@modules/开头的
  return content.replace(/ from ['|"]([^'"]+)['|"]/g,function(s0,s1){
    // console.log(s0,s1)
    if(s1[0]!=='.'&&s1[1]!=='/'){
      return ` from '/@modules/${s1}'`
    }else{
      return s0
    }
  })
}

//  这里图了方便 直接使用了中间件。
app.use((ctx, next)=>{
  const { request: { url } } = ctx
  if (url === '/') { // 发现当请求的是根目录是否 返回index 文件
    let content = fs.readFileSync('./index.html', 'utf-8')
    ctx.type = 'text/html'
    ctx.body = content
  }
  next()
})


app.listen(8888, ()=>{
  console.log('http://localhost:8888/')
})

Затем мы запускаем службу и получаем доступ к пути.

  1. Мы видим, что. Мы успешно запросилиlocalhost

  2. index.htmlвнутри

    <script type="module" src="/src/main.js"></script>Запрос не был успешно отправлен.

Вторая интеграция js

// 在原有代码的基础上 添加    记得一定要写next()方法 否则 不会执行下一个 中间件
app.use((ctx, next)=>{
  const { request: { url } } = ctx
  if (url.endsWith('.js')) {
    console.log(url.slice(1)) // 打印出请求路径
    const p = path.resolve(__dirname, url.slice(1)) // 找到对应文件
    const content = fs.readFileSync(p, 'utf-8') // 读取文件
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(content) // 吧一些以  路径进行更换
  }
  next()
})

Отсюда мы можем видеть

  1. Но есть и красные. не удалось получить ответ. то естьmain.jsНекоторые, такие какimport { createApp } from 'vue'Эти. Мы не нашли. Итак, следующая задача — сделать их доступными для поиска.

Третий шаг - изменить путь к модулям

каждый может видетьVueПуть запросаhttp://localhost:8888/@modules/vue. Но у нас нет этого пути **@** в нашей папке. Поэтому нам нужно специальное лечение для этого.

app.use((ctx, next)=>{
  const { request: { url } } = ctx
  if (url.startsWith('/@modules/')) {
    const prefix = path.resolve(__dirname, 'node_modules', url.replace('/@modules/', ''))
    console.log(prefix)  // 打印出来的  是拼接后的路径
    const module = require(prefix + '/package.json').module
    console.log(module) // 获取 vue/module的路径
    const p = path.resolve(prefix, module)
    console.log(p) // 再次拼接读取
    const ret = fs.readFileSync(p, 'utf-8')
    ctx.type = 'application/javascript'
    ctx.body = rewriteImport(ret)
  }
  next()
})

Главное отметить. Это чтение и объединение пути к файлу. Может быть немного запутанно. Крупный шрифт несколько раз. Познакомьтесь с каждым местом

В этот момент. мы можем запроситьVueохватывать

Четвертый шаг — разобрать файл Vue и вернуть

  • Здесь нужно использовать два официально предоставленных модуля

    1. @vue/compiler-sfc: Официальныйvueанализатор одного файла
    2. @vue/compiler-dom: проходить черезпарсер, преобразование, генерация,Будувиртуальный домвизуализировать в браузеренастоящий дом
    app.use((ctx, next)=>{
      const { request: { url,query } } = ctx
      if (url.indexOf('.vue') > -1) {
        // import xx from 'xx.vue'
        // 1. 单文件组件解析
        console.log(123456)
        const p = path.resolve(__dirname, url.split('?')[0].slice(1))
        // 解析单文佳年组建,需要官方的库
        const { descriptor } = compilerSfc.parse(fs.readFileSync(p, 'utf-8'))
        if (!query.type) {
          // js内容
          ctx.type = 'application/javascript'
          ctx.body = `
    ${ rewriteImport(
              descriptor.script.content.replace('export default ',
                  'const __script = ')) }
    import {render as __render} from "${ url }?type=template"
    __script.render = __render
    export default __script
          `
        } else if (query.type == 'template') {
          // 解析我们的template 编程render函数
          const template = descriptor.template
          const render = compilerDom.compile(template.content, { mode: 'module' }).code
          ctx.type = 'application/javascript'
          ctx.body = rewriteImport(render)
        }
      }
      next()
    })
    

Тогда все узнают.APP.vueбыл идентифицирован и возвращен

но. Отсутствие эффекта. Почему это?

После некоторого Baidu. оказывается одного не хватаетprocess对象. Это вnode·Только. Не на стороне браузера. так. Давайте перепишемпервый шаг

app.use((ctx, next)=>{
  const { request: { url } } = ctx
  if (url === '/') {
    let content = fs.readFileSync('./index.html', 'utf-8')
    content = content.replace('<script', `
      <script>
        window.process = {
          env: {NODE_EV:'dev'}
        }
      </script>
      <script
    `)
    ctx.type = 'text/html'
    ctx.body = content
  }
  next()
})

так. Мы дали браузеруprocessАтрибуты. может быть идентифицирован

последний шаг. Разобрать CSS

с разборомjsпочти

app.use((ctx, next)=>{
  const { request: { url } } = ctx
  if (url.endsWith('.css')) {
    const p = path.resolve(__dirname, url.slice(1))
    const file = fs.readFileSync(p, 'utf-8')
    const content = `
      const css = "${ file.replace(/\n/g, '') }"
      const link = document.createElement('style')
      link.setAttribute('type', 'text/css')
      document.head.appendChild(link)
      link.innerHTML = css
      export default css
    `
    ctx.type = 'application/javascript'
    ctx.body = content
  }
  next()
})

Затем мы идентифицировали все файлы

Суммировать

Если вы хотите добавить другие файлы. Просто следуйте этому методу и загрузите его соответствующим образом.

Обнаружены нерешенные проблемы

  1. Когда наша начальная сборка завершена. Сначала проанализируйтеCSS.cssмалоэффективен, но вводитVueЭти две официальные библиотеки работали.

    Если есть вопросы могу помочь. Великолепный

Суммировать

Эта статья в основном обобщает.

  1. Новый синтаксис Vue3 и его использование
  2. Несколько небольших примеров разработки Vue + TS
  3. Понимание и простая имитация Vite