Web开发-Vue3.0上手 如果你忘了 看这一pian就够了!

Web开发-Vue3.0上手 如果你忘了 看这一pian就够了!

之前用Vue3.0开发过一个项目雏形,后续需求尚未跟进,有些忘了,so,在继续开发之前,没事看看Vue3的知识,简单回顾,强化技能;

Study from 2021 Vue3.0极速上手

vue3基础知识参考Web开发-Vue3.x

主要内容

相比Vue2的主要变化:

  • Fragments和Emits
  • 自定义渲染器
  • 全局API调整
  • 摇树优化
  • v-model调整
  • 更好用的渲染函数
  • 失宠的函数式组件
  • 异步组件的使用
  • data选项、组件白名单
  • transition类名调整
  • watch API重要调整
  • $on $off $once被删除了
  • filter为何也被移除
  • 。。。

TodoList实践;

vue-router 4.x

  • 新用法 新特性
  • history选项
  • 通配处理
  • isReady方法
  • 滚动行为变化
  • keep-alive和transition与router-view使用的重要变化
  • router-link使用调整
  • 动态路由解析变化
  • composition API

vuex 4.x

  • 新用法
  • composition API

如何快速开始Vue3.0

  • cdn方式
  • vue-cli(> v4.5):
    • npm i -g @vue/cli@next
    • 创建项目时,会有相应的vue3选项;
  • vite:更快捷
    • npm init vite-app <project-name>
    • cd <project-name>
    • npm install
    • npm run dev

使用vite开发时,模块间引入文件的后缀名不可省略;

相比Vue2的主要变化:

composition API

更好的逻辑复用和代码组织


<p> {{ counter.counter }} </p>
<p> {{ counter.doubleCounter }} </p>

import {reactive, computed, onMountd, onUnmounted} from 'vue'
exprot default {
  name:'',
  props:{
    msg:String
  },
  setup() {
    // counter 相关操作逻辑
    // 响应式对象
    const data = useCounter()
    // or 不提取
    //  const data = reactive({
    //    counter: 1,
    //   //  计算属性
    //    doubleCounter: computed(()=> data.counter * 2)
    //  })

    // let timer
    // //  声明周期监听回调
    // onMountd(()=>{
    //   // 定义定时器
    //   timer = setInterval(()=>{
    //     data.counter++
    //   }, 1000)
    // })

    // onUnmounted(()=>{
    //   // 取消定时器
    //   clearInterval(timer)
    // })


    // 其他逻辑
    const msg2 = ref('something')

     return {data, msg2}
  }
}

// 提取:counter 相关操作逻辑
function useCounter() {
    const data = reactive({
       counter: 1,
      //  计算属性
       doubleCounter: computed(()=> data.counter * 2)
     })

    let timer
    //  声明周期监听回调
    onMountd(()=>{
      // 定义定时器
      timer = setInterval(()=>{
        data.counter++
      }, 1000)
    })

    onUnmounted(()=>{
      // 取消定时器
      clearInterval(timer)
    })

    return data
}

// 对象解包
const {counter, doubleCounter} = useCounter() 

function useCounter() {
  ...
  // 可以把响应式对象中的各个key 转换为单值响应式的ref数据
  return toRefs(data)
}
// ref引用 DOM元素
<p ref='desc'></p>

...

setup() {
  // ref响应式数据
  const {counter, doubleCounter} = useCounter()
  // reactive响应式数据
  const data = useCounter()  
  ...
  // 使用元素引用
  const desc = ref(null)

  // ref监听器
  watch(counter, (val, oldVal) => {
    // 访问ref单值响应式数据 需要使用.value属性
    const pElement = desc.value;
    pElement.textContent = `value change ${val} ${oldValue}`
  })

  // reactive监听器
  watch(() => data.counter, (val, oldVal) => {
    // 访问ref单值响应式数据 需要使用.value属性
    const pElement = desc.value;
    pElement.textContent = `value change ${val} ${oldValue}`
  })

  return {data, counter, doubleCounter, desc}
}

Teleport

传送门组件:提供了一种简洁的方式可以指定它里面内容的父元素

<template>
  <button @click="modalOpen = true">弹出一个全屏模态窗口</button>

  <!-- 传送组件是一个全局组件 包含的就是要传送的内容 to指定的是一个选择器-->
  <teleport to="body">
    <div v-if="modalOpen" class="modal">
      <div>
        这是一个模态窗口
        我的父元素是body
        <button @click="modalOpen=false">Close</button>
      </div>
    </div>
  </teleport>
</template>
<script>
export default {
  setup(){
    let modalOpen = ref(true)
  }
}
</script>
<style scoped>
.modal {
  position: absolute;
  top:0; right:0; bottom:0; left:0;
  background-color:rgba(0,0,0,0.5);
  display:flex;
  flex-direction: column;
  align-items: center;
  justify-content:center;
}

.modal div {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content:center;
  background-color:white;
  width: 300px;
  height: 300px;
  padding: 5px;
}

</style>
Fragments

Vue3中组件可以拥有多个根;

<template>
  <header>..
  <main>..
  <footer>..
</template>
Emits Component Option

vue3中组件发送的自定义事件需要定义在emits选项中:

// 定义组件 Emit
<Emit>
  <template>
    <div>
      // 如果与原生事件名重名 那么该事件会被触发两次 通过在emits选项中进行配置 可以使触发只有一次
      <p @click="$emit('click')">Click</p>
      // 自定义事件会被触发 这可以更好的指示组件工作
      <p @click="$emit('myClick')">MyClick</p>
    </div>
  </template>

  export default {
    // emits选项
    emits: ['click','myClick']
  }
</Emit>

// 使用组件 Emit
<Emit @click='onClick'></Emit>
<Emit @myClick='onClick'></Emit>

自定义渲染器 custom renderer

这个API可以用来自定义渲染逻辑;如自定义一个画布的渲染过程;

全局API调整

Global API改为应用程序实例调用

  • Vue2没有app概念,new Vue()得到的根实例被作为app;这样的根实例是共享相同的全局配置,这在测试时会污染其他测试用例;Vue.xxx
  • 全局配置也导致没有办法在单页面创建不同全局配置的多个app实例;
  • vue3中使用createApp返回app实例,由它暴露一系列全局api;app.xxx

主要受影响的API:

  • Vue.config <=> app.config
  • Vue.component <=> app.component
  • Vue.directive <=> app.directive
  • Vue.mixin <=> app.mixin
  • Vue.use <=> app.use
摇树优化

vue2中不少global-api是作为静态函数直接挂在在构造函数上,如Vue.nextTick(),如果代码中未用到,会形成dead code,这类代码无法使用webpack的tree-shaking排除掉;

vue3中则将他们抽取为独立函数,这样打包工具的摇树优化可以将这些dead code排除掉;

import {nextTick} from 'vue'

nextTick(() => {
  // 
})

v-model调整

model选项 和 v-bind的sync修饰符被移除,统一为v-model参数形式;

// 示例:在子组件中修改传入的父组件值
// 父组件部分
<SubComponent v-model:counter='counter'></SubComponent>
// 等价于下面这个,这也是我们在vue2中使用的方式 显然现在更简洁了
// <SubComponent :counter='counter' @update:counter="counter=$event"></SubComponent>

// 子组件部分
<template>
  <div @click="$emit('update:counter', counter + 1)"> Counter: {{counter}}
  </div>
</template>

export default {
  props: {
    // 父组件如果不指定字段名 默认展开的字段名为 modelValue
    counter: {
      type: Number,
      default:0 
    },
  },
}

更好用的渲染函数API
  • 不再传入h函数,需要手动导入;
  • 拍平 props结构;
  • 作用域插槽scopedSlots删掉了,统一到slots;

<RenderTest>
  <template>默认插槽</template>
  <template v-slot:content>具名插槽</template>
</RenderTest>

import {h} from 'vue'

componets:{
  RenderTest: {
    render(){
      ...
    }
  }
}
// ...的具体实现
render() {
  const emit = this.$emit
  const modelValue = this.modelValue
  const onclick = this.onclick

  // 获取插槽内容
  this.$slots.default()
  this.$slots.content()

  retrun h('div', [
    h('div', {
      onClick() {
        emit('update:modelValue', modelValue + 1)
      },
      `I am comp, ${modelValue}`
    }),
    h('button', {
      onClick() {
        onclick()
      },
      'buty it!'
    })
  ])
}
失宠的函数式组件

函数式组件仅能通过简单函数方式创建,functional选项废弃;

异步组件的使用

异步组件要求使用defineAsyncComponent方法创建;

  • 必须使用defineAsyncComponent包裹
  • component选项重命名为loader
  • Loader函数不在接收resolveandreject且必须返回一个Promise;
import {defineAsyncComponent} from 'vue'

// 不带配置的异步组件
const asyncPage = defineAsyncComponent(()=> import('./NextPage.vue'))

// 带配置的异步组件(这里loader选项就是以前的component选项)
import ErrorComponent from './components/ErrorComponent.vue'
import LoadingComponent from './components/LoadingComponent.vue'

const asyncPageWithOptions = defineAsyncComponent({
  loader: () => import('./NextPage.vue'),
  delay: 200,
  timeout: 3000,
  errorComponent: ErrorComponent,
  loadingComponent: LoadingComponent
})


data选项

组件data选项应该总是声明为函数,返回响应式数据;

组件白名单

vue3中 自定义元素检测发生在模板编译时,如果要添加一些vue之外的自定义元素,避免编译时的检测,需要在编译器选项中设置isCustomElement选项;

使用构建工具时,模板都会用vue-loader预编译,设置它提供的compilerOptions即可:

// vue.config.js
rules: [
  {
    test: /\.vue$/,
    use: 'vue-loader',
    options: {
      compilerOptions: {
        // 标签为plastic-button时 放过,可以设置多个
        isCustomElement: tag => tag === 'plastic-button'
      }
    }
  }
  // ...
]

使用vite演示的话,在vite.config.js中配置 vueCompilerOptions即可:

module.exports = {
  vueCompilerOptions: {
    isCunstomElement: tag => tag === 'piechart'
  }
}

如果采用的是运行时编译的vue,可以通过全局配置isCunstomElement:

const app = Vue.createApp({})
app.config.sCunstomElement = tag => tag === 'piechart'
is属性仅限于用在component标签上

vue3设置动态组件,使用is可以确定实际渲染的组件是哪一个;但在vue3中,is属性仅限于用在component标签上;

dom内模板解析可以使用v-is指令替代;

<table>
  // 实际渲染的是blog-post-row组件
  <tr v-is="'blog-post-row'" :data='xxx'></tr>
</table>

component('blog-post-row', {
  props: ['data'],
  template: "<tr><td>{{this.data}}</td></tr>"
})


自定义指令API 和 组件保持一致
bind -> beforeMount
inserted -> mounted
beforeUpdate
update -> [removed]
componnetUpdated -> updated
beforeUnmount
unbind -> unmounted
createApp(App).directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

// 使用
<p v-highlight></p>
transition类名调整
  • v-enter -> v-enter-from
  • v-leave -> v-leave-from

  v-enter-from
v-enter-active
  v-enter-to


  v-leave-from
v-leave-active
  v-leave-to

<transition name='fade'>
  <p v-if="show"> hellow </p>
</transition>

<style scoped>
// 淡入淡出
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fage-enter-from,
.fade-leave-to {
  opacity: 0;
}

<style>
watch API重要调整

组件watch选项和实例方法$watch不再支持点分隔符字符串路径

可以使用计算函数作为$watch的参数实现对带路径的支持;

this.$watch(()=> this.foo.bar, (v1,v2) => {

})
keyCode 作为v-on修饰符被移除

keyCode 作为v-on修饰符被移除,只支持别名方式

// keycode不支持
<input v-on:keyup.13="submit" />
// alias支持
<input v-on:keyup.enter="submit" />

$on $off $once被删除了

这三个被认为是不应该由vue提供的,可以使用其他第三方库实现;

<script src="https://unpkg.com/mitt/dist/mitt.umd.js"></script>

// 创建emitter(放在公共的地方 并且导出)
export const emitter = mitt()
// import {emitter} from ...

// 发送事件
emitter.emit('foo', 'foooooo')

// 监听事件
emitter.on('foo', msg => console.log(msg))

filter也被移除了;

TodoList实践

MVC实现

功能预览:

在这里插入图片描述

代码实现(Vue3):

<!-- Todos.vue -->
<template>
  <div>
    <input
      type="text"
      v-model="newTodo"
      @keyup.enter="addTodo"
      autofocus
      placeholder="新增今日待办"
      autocomplete="off"
    />
    <ul>
      <li
        v-for="todo in filterdTodos"
        :key="todo.id"
        :class="{completed: todo.completed, editing: todo === editedTodo}">
        <div class="view">
          <input type="checkbox" v-model="todo.completed">
          <label @dblclick="editTodo(todo)">{{todo.title}}</label>
          <button @click="removeTodo(todo)">X</button>
        </div>
        <!-- 双击:编辑待办 -->
        <!-- 我们希望双击时 input可以自动获取焦点 这样体验更好 这里应用到自定义指令 -->
        <input
          type="text"
          class="edit"
          v-model="todo.title"
          v-todo-focus="todo=== editedTodo"
          @blur="doneEdit(todo)"
          @keyup.enter="doneEdit(todo)"
          @keyup.escape="cancelEdit(todo)"
        />
      </li>
    </ul>
    <!-- 过滤功能:计算属性 + watch的应用 -->
    <p class="filters">
      <span @click="visibility = 'all'" :class="{selected: visibility === 'all'}">All</span>
      <span @click="visibility = 'active'" :class="{selected: visibility === 'active'}">Active</span>
      <span @click="visibility = 'completed'" :class="{selected: visibility === 'completed'}">Completed</span>
    </p>
  </div>
</template>

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

const filters = {
  all (todos) {
    console.log('tag', 'all')
    return todos
  },
  active (todos) {
    console.log('tag', 'active')
    return todos.filter(todo => !todo.completed)
  },
  completed (todos) {
    console.log('tag', 'completed')
    return todos.filter(todo => todo.completed)
  }
}

// 缓存操作:刷新数据不丢失
const todoStorage = {
  fetch () {
    const todos = JSON.parse(localStorage.getItem('vue3-todos') || '[]')
    todos.forEach((todo, index) => {
      todo.id = index + 1
    })
    return todos
  },
  save (todos) {
    localStorage.setItem('vue3-todos', JSON.stringify(todos))
  }
}

export default {
  setup () {
    const state = reactive({
      newTodo: '',
      beforeEditCache: '', // 缓存编辑前的title
      editedTodo: null,
      todos: todoStorage.fetch(),
      visibility: 'all',
      filterdTodos: computed(() => {
        // 计算属性
        return filters[state.visibility](state.todos)
      })
    })

    function addTodo () {
      state.todos.push({
        id: state.todos.length + 1,
        title: state.newTodo,
        completed: false
      })
      state.newTodo = ''
    }

    function editTodo (todo) {
      state.beforeEditCache = todo.title
      state.editedTodo = todo
    }

    function removeTodo (todo) {
      state.todos.splice(state.todos.indexOf(todo), 1)
    }

    function doneEdit (todo) {
      // 数据的变更 已经 由双向绑定完成 这里只需要把editTodo置空即可
      state.editedTodo = null
    }

    function cancelEdit (todo) {
      todo.title = state.beforeEditCache
      state.editedTodo = null
    }

    // 监听副作用
    watchEffect(() => {
      // 只要todos改变 就缓存
      todoStorage.save(state.todos)
    })

    return {
      ...toRefs(state),
      addTodo,
      editTodo,
      removeTodo,
      doneEdit,
      cancelEdit
    }
  },
  directives: {
    'todo-focus': (el, { value }) => {
      if (value) {
        el.focus()
      }
    }
  }

}
</script>

<style scoped>

.completed label{
  /* 删除装饰线 */
  text-decoration: line-through;
}

.edit,
.editing .view {
  display: none;
}

.view,
.editing .edit {
  display: block;
}

.filters > span {
  cursor: pointer;
  padding: 5px 10px;
  border: 1px solid transparent;
}
.filters > span.selected {
  border: 1px solid black;
  border-radius: 5px;
}

</style>

组件化实践

针对上边的Todos进行组件化优化重构;

先来看看利用组件化和 Composition API 优化后的Todos.vue

<template>
  <div>
    <EditTodo
      v-model:todo-title="newTodo"
      @keyup.enter="addTodo"
      autofocus
      placeholder="新增今日待办"
      autocomplete="off"
    ></EditTodo>
    <ul>
      <TodoItem
        v-for="todo in filterdTodos"
        :key="todo.id"
        :todoC="todo"
        v-model:edited-todo="editedTodo"
        @remove-todo="removeTodo"
      ></TodoItem>
    </ul>
    <!-- 过滤功能:计算属性 + watch的应用 -->
    <Filter
      :items="filterItems"
      v-model="visibility"
    ></Filter>
  </div>
</template>

<script>
import { reactive, toRefs } from 'vue'
import TodoItem from './TodoItem.vue'
import Filter from './Filter.vue'
import { useTodos } from './useTodos.js'
import { useFilter } from './useFilter.js'

export default {
  components: {
    TodoItem,
    Filter
  },
  setup () {
    const todoState = reactive({
      newTodo: '',
      editedTodo: null
    })
    const { todos, addTodo, removeTodo } = useTodos(todoState)
    const filterState = useFilter(todos)

    return {
      ...toRefs(todoState),
      ...toRefs(filterState),
      addTodo,
      removeTodo
    }
  }
}
</script>

<style scoped>

</style>

具体的变化:

  • 组件部分:
    • input标签进行了组件封装,参考EditTodo.vue
    • 值得注意的是EditTodo组件是在main.js中被注册为全局组件,参考main.js
    • li标签及其内容进行了组件提取,参考TodoItem.vue
    • 过滤的三个功能按钮进行了组件化封装,参考Filter.vue
  • 组合API调整:
    • Todos相关操作(添加、移除、缓存)的逻辑状态 提取到了useTodos.js中;
    • Fliter相关状态(响应式) 提取到了useFilter.js中;
    • 之后将组合之后的状态、操作 return出去;
EditTodo.vue

$attrs

<template>
  <input
      type="text"
      :value="todoTitle"
      @input="onInputChange"
      v-bind="$attrs"
    />
</template>

<script>
export default {
  props: {
    todoTitle: {
      type: String,
      default: ''
    }
  },
  methods: {
    onInputChange (e) {
      this.$emit('update:todoTitle', e.target.value)
    }
  }

}
</script>

<style>

</style>

main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import EditTodo from './components/todos/EditTodo.vue'

createApp(App)
  .component('EditTodo', EditTodo) // 全局注册组件(局部注册与之前一致)
  .directive('hightlight', { // 注册全局指令
    beforeMount (el, binding, vnode) {
      el.style.background = binding.value
    }
  })
  .use(store)
  .use(router)
  .mount('#app')
TodoItem.vue

emit配置项、自定义指令

<template>
  <li
    :class="{completed: todo.completed, editing: todo === editedTodo}">
    <div class="view">
      <input type="checkbox" v-model="todo.completed">
      <label @dblclick="editTodo(todo)">{{todo.title}}</label>
      <button @click="removeTodo(todo)">X</button>
    </div>
    <!-- 双击:编辑待办 -->
    <!-- 我们希望双击时 input可以自动获取焦点 这样体验更好 这里应用到自定义指令 -->
    <EditTodo
      class="edit"
      v-model:todo-title="todo.title"
      v-todo-focus="todo=== editedTodo"
      @blur="doneEdit(todo)"
      @keyup.enter="doneEdit(todo)"
      @keyup.escape="cancelEdit(todo)"
    ></EditTodo>
  </li>
</template>

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

export default {
  props: {
    todoC: {
      type: Object,
      required: true
    },
    editedTodo: Object
  },
  // emit配置项
  emits: ['remove-todo', 'update:edited-todo'],
  setup (props, { emit }) {
    const state = reactive({
      todo: props.todoC, // 避免v-model绑定修改单向数据流造成的异常
      beforeEditCache: '' // 缓存编辑前的title
    })

    function editTodo (todo) {
      state.beforeEditCache = todo.title
      emit('update:edited-todo', todo)
    }

    function removeTodo (todo) {
      emit('remove-todo', todo)
    }

    function doneEdit (todo) {
      // 数据的变更 已经 由双向绑定完成 这里只需要把editTodo置空即可
      emit('update:edited-todo', null)
    }

    function cancelEdit (todo) {
      todo.title = state.beforeEditCache
      emit('update:edited-todo', null)
    }

    return {
      ...toRefs(state),
      editTodo,
      removeTodo,
      doneEdit,
      cancelEdit
    }
  },
  directives: {
    'todo-focus': (el, { value }) => {
      if (value) {
        el.focus()
      }
    }
  }
}
</script>

<style scoped>

.completed label{
  /* 删除装饰线 */
  text-decoration: line-through;
}

.edit,
.editing .view {
  display: none;
}

.view,
.editing .edit {
  display: block;
}
</style>

Filter.vue
<template>
  <p class="filters">
    <span
      v-for="item in items"
      :key='item.value'
      @click="$emit('update:modelValue', item.value)"
      :class="{selected: modelValue === item.value}">{{item.label}}</span>
  </p>
</template>

<script>
export default {
  props: ['items', 'modelValue'],
  emits: ['update:modelValue']
}
</script>

<style scoped>
.filters > span {
  cursor: pointer;
  padding: 5px 10px;
  border: 1px solid transparent;
}
.filters > span.selected {
  border: 1px solid black;
  border-radius: 5px;
}
</style>


useTodos.js

watchEffect


import { ref, watchEffect } from 'vue'

// 缓存操作:刷新数据不丢失
const todoStorage = {
  fetch () {
    const todos = JSON.parse(localStorage.getItem('vue3-todos') || '[]')
    todos.forEach((todo, index) => {
      todo.id = index + 1
    })
    return todos
  },
  save (todos) {
    localStorage.setItem('vue3-todos', JSON.stringify(todos))
  }
}

export function useTodos (state) {
  const todos = ref(todoStorage.fetch())

  function addTodo () {
    todos.value.push({
      id: todos.value.length + 1,
      title: state.newTodo,
      completed: false
    })
    state.newTodo = ''
  }

  function removeTodo (todo) {
    todos.value.splice(todos.value.indexOf(todo), 1)
  }

  // 监听副作用
  watchEffect(() => {
    // 只要todos改变 就缓存
    todoStorage.save(todos.value)
  })

  return {
    todos,
    addTodo,
    removeTodo
  }
}

useFilter.js
import { reactive, computed } from 'vue'

const filters = {
  all (todos) {
    console.log('tag', 'all')
    return todos
  },
  active (todos) {
    console.log('tag', 'active')
    return todos.filter(todo => !todo.completed)
  },
  completed (todos) {
    console.log('tag', 'completed')
    return todos.filter(todo => todo.completed)
  }
}

export function useFilter (todos) {
  const filterState = reactive({
    filterItems: [
      { label: 'All', value: 'all' },
      { label: 'Active', value: 'active' },
      { label: 'Completed', value: 'completed' }
    ],
    visibility: 'all',
    filterdTodos: computed(() => {
      // 计算属性
      return filters[filterState.visibility](todos.value)
    })
  })

  return filterState
}


vue-router 4.x

  • <router-view>
  • <router-link to="/">
// 路由实例注册:
createApp(App)
  .use(router)
  .mount('#app')

// 路由实例创建
import {createRouter, createWebHistory} from 'vue-router'
const routes = [{
    path: '/user',
    name: 'user-layout',
    component: () => import('@/layout/user-layout/user-layout'),
    redirect: '/user/login',//如果将第一个child的path设置为空串 这里可以不使用redirect 重定向
    children: [{
      path: '/user/login',
      name: 'login',
      component: () => import('@/views/user/login')
    }]
  },
  {
    path: 'path: "/:pathMatch(.*)*"',
    name: '404',
    // 每个路由中都可以携带一个元数据对象 用于携带信息
    meta: {
      auth: false,
      title: 'notFound'
    },
    component: () =>
      import( /* webpackChunkName: "fail" */ '@/views/error/notFound')
  }
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

// 路由动态添加
router.addRoute({
  path:'/about',
  name:'about',
  component: () => import('./components/About.vue')
})

router.addRoute('about',{
  path: './about/info',
  component: {
    render() {
      return h('div', 'info page')
    }
  }
})

// 结合组合API的使用
import {useRouter, useRoute} from "vue-router"
import {watch} from "vue"

setup(){
  const router = useRouter();
  const route = useRoute();

  // 监听route
  watch(()=>route.params,(newparams)=>{

  })

  watch(()=>route.query,(newquery)=>{

  })
  // 守卫
  onBeforeRouteLeave((to,from) => {
    const answer = window.confirm("Sure Leave?")
    if (!answer){
      return false
    }
  })
  onBeforeRouteUpdate((to,from) => {
    
  })

  return {}
}
<!-- // 新接口 useLink的使用(如包装 router-link标签称为更高级的组件)
// 创建 NavLink.vue -->
<template>
  <div :class="{active: isActive}" @click="navigate">
    {{route.name}}
  </div>
</template>

<script>
import {RouterLink, useLink} from 'vue-router'

export default {
  props: {
    ...RouterLink.props,
    inactiveClass: String
  },
  setup(props) {
    const {route, href, isActive, isExactActive, navigate} = useLink(props)

    return {route, isActive, navigate}
  }
}
</script>

<style scoped>
.active {
  background-color: #3fb983;
}
</style>

history选项 代替了mode选项

  • history => createWebHistory('/base-prefix') 可以指定公共的路由前缀
  • hash => createWebHashHistory()
  • abstract => createMemoryHistory() 用于服务端渲染

base选项移至 createWebHistory方法中;

通配符(*)被移除了,如而代之的是:

  • path: '*' => path: "/:pathMatch(.*)*"
// 使用命名导航至404页面
router.resolve({
  name:'404',
  params: {
    pathMatch: ['not', 'found']
  }
})
  • isReady() 替代了 onReady()
router.push()
// before
router.onReady(onSuccess, onError)
// new
router.isReady().then(onSuccess).catch(onError)
  • scrollBehavior行为发生了变化
const router = createRouter({
  history:...,
  routes:[],
  scrollBehavior(to, from, savedPosition){
    // {x:10, y:10} before
    // {left:10, top:10} new
    if (savedPosition) {
      // 记录并设置 页面的滚动位置
      return savedPosition
    }ekse {
      return {top: 10}
    }
  }
})
  • keep-alivetransition 必须用在 router-view内部
    • 以前是在外边的
<router-view v-slot="{Component}">
  <keep-alive>
    <component :is="Component" />
  </keep-alive>
</router-view>
  • router-link移除了一些属性
    • append属性
    • tag/event
    • exact:完全比配逻辑简化了 仅支持完全相同
// before append
<router-link to="child-route" append>
// byself
<router-link :to="append($route.path, 'child-route')">

app.config.globalPropertier.append = (path, pathToAppend) => {
  return path + pathToAppend
}

// before tag
<router-link to="/xx" tag="span" event="dblclick"></router-link>
// new 
<router-link to="/xx" custom v-slot="{navigate}">
  <span @dblclick="navigate">xxx</span>
</router-link>

  • mixins中的路由守卫将被忽略;
  • match方法被移除了,使用resolve替代;
    • resolve返回的是一个location,多数路由跳转还是用push,push返回的是一个promise;
  • 移除router.getMatchedComponents(),用于服务端渲染的;
    • 可以用router.currentRoute.value.matched这种方式访问匹配的路由;
  • 包括首屏导航在内所有导航均为异步;
    • 如果首屏存在路由守卫,则可以不等待就绪就直接挂载
// 如果需要首屏前动画
app.use(router)
router.isReady().then(()=> app.mount('#app'))
  • route的parent属性被移除
    • 替换:const parent = this.$route.matched[this.$route.matched.length - 2]
createRouter({
  strict: boolean,
  sensitive: boolean
})
  • router创建时 routes选项是必填项;
  • 跳转不存在的命名路由 会报错;
  • 必填参数缺失会抛异常;
  • 命名子路由 如果path为空 将不再追加/
    • 这可能会给设置了重定向redirect选项的子路由带来副作用;

vuex 4.x

vuex4是vuex3的兼容版本,提供和vuex3相同的API;

// 创建store实例
import {createStore} from 'vuex'

const store = createStore({
  state() {
    return {
      count: 1
    }
  },
  mutations: {
    add(state){
      state.count++;
    }
  }
})

app.use(store)



// composition API
import {useStore} from 'vuex'

const store = useStore()

return  {
  ...toRefs(store.state),
  add() {
    store.commit('add')
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值