👆 vue3新特性对普通开发者、二次开发者(跨段)、源码维护者带来的好处
// 在vue实例上挂载全局组件
Vue.component('el-counter', {
data(){
return {count: 1}
},
template: '<button @click="count++">Clicked {{ count }} times.</button>'
})
// 引入vue-router
let VueRouter = require('vue-router')
// 在vue实例上通过use全局挂载router插件
Vue.use(VueRouter)
但是由于全局的 Vue 只有一个,所以当我们在一个页面的多个应用中独立使用 Vue 就会非常困难。 (基本不会有。。。
Vue.component('el-counter',...)
new Vue({el:'#app1'})
new Vue({el:'#app2'})
为了解决这个问题,Vue 3 引入一个新的 API ,createApp,来解决这个问题,也就是新增了 App 的概念。全局的组件、插件都独立地注册在这个 App 内部,很好的解决了上面提到的两个实例容易造成混淆的问题。
const { createApp } = Vue
const app = createApp({})
app.component(...)
app.use(...)
app.mount('#app1')
const app2 = createApp({})
app2.mount('#app2')
createApp
还移除了很多我们常见的写法,比如在 createApp
中,就不再支持 filter、$on、$off、$set、$delete
等 API slot
和 slot-scope
两者实现了合并,而 directive
注册指令的 API 等也有变化
@vue/compat 还可以很好地帮助你学习版本之间的差异 “阿里妈妈”出品的 gogocode,通过编译原理,由vue2代码转AST,替换AST成vue3,再生成vue3代码
vite官方收集的一些和vite相关的项目,包括插件,模板,都分类整理好的,地址如下 https://github.com/vitejs/awesome-vite
- vuex 的数据本地持久化插件
- 接口数据的mock, json-server
- 埋点的sdk
- @vueuse 库,封装常用的hooks
集成 EditorConfig 配置,集成 husky 和 lint-staged,代码提交时候检查 ESLint 规则,通过再提交
Composition API 和 <script setup>
而让你感到好奇的 <script setup>
本质上是以一种更精简的方式来书写 Composition API
对于 ref 返回的响应式数据,我们需要修改 .value 才能生效,而在 <script setup>
标签内定义的变量和函数,都可以在模板中直接使用
直接 import TodoList.vue 组件,然后 <script setup>
会自动把组件注册到当前组件
没有setup的compositiob写法
import { ref } from "vue";
export default {
setup() {
let count = ref(1)
function add() {
count.value++
}
return {
count,
add
}
}
}
setup语法
import { ref, computed } from "vue";
let count = ref(1)
function add(){
count.value++
}
let { title, todos, addTodo, clear, active, all, allDone } = useTodos();
vue style标签使用js中的变量
<style scoped>
h1 {
color:v-bind(color);
}
</style>
Composition API 和 <script setup>
虽然能提高开发效率,但是带来的一些新的语法 比如 ref 返回的数据就需要修改 value 属性 响应式和生命周期也需要 import 后才能使用等等
对于value,官方是这样解释的: 将值封装在一个对象中,看似没有必要 但为了保持 JavaScript 中不同数据类型的行为统一,这是必须的 这是因为在 JavaScript 中,Number 或 String 等基本类型是通过值而非引用传递的 在任何值周围都有一个封装对象,这样我们就可以在整个应用中安全地传递它,而不必担心在某个地方失去它的响应性
关于导入问题,antfu大神有一个插件unplugin-auto-import,可以自动注入依赖项,不用import
https://github.com/antfu/unplugin-auto-import
我们可以在 loading 状态下,去修改浏览器的小图标 favicon
import {ref,watch} from 'vue'
export default function useFavicon( newIcon ) {
const favicon = ref(newIcon)
const updateIcon = (icon) => {
document.head
.querySelectorAll(`link[rel*="icon"]`)
.forEach(el => el.href = `${icon}`)
}
const reset = ()=>favicon.value = '/favicon.ico'
watch( favicon,
(i) => {
updateIcon(i)
}
)
return {favicon,reset}
}
React 主要构建 App 还是用 Class Component,当然也有 functional component
这俩区别就在于 class 有状态,functional 比较“纯粹”没有状态
现在 React 都用 “functional” 组件,但是有“状态”的,状态哪里来的 “Hook” 过来的,钩过来的。 组件“不负责”维护状态,useXXX 去管理了。
5星评星组件 从中间往两边切割
<template>
<div>
{{rate}}
</div>
</template>
<script setup>
import { defineProps,computed } from 'vue';
let props = defineProps({ // 定义组件接受参数,使用方式是props.xxx,而不是this.xxx
value: Number
})
let rate = computed(()=>"★★★★★☆☆☆☆☆".slice(5 - props.value, 10 - props.value))
</script>
<template>
<div :style="fontstyle">
<div class='rate' @mouseout="mouseOut">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">☆</span>
<span class='hollow' :style="fontwidth">
<span @mouseover="mouseOver(num)" v-for='num in 5' :key="num">★</span>
</span>
</div>
</div>
</template>
<script setup>
// ...其他代码
// 评分宽度
let width = ref(props.value)
function mouseOver(i){
width.value = i
}
function mouseOut(){
width.value = props.value
}
const fontwidth = computed(()=>`width:${width.value}em;`)
</script>
<style scoped>
.rate{
position:relative;
display: inline-block;
}
.rate > span.hollow {
position:absolute;
display: inline-block;
top:0;
left:0;
width:0;
overflow:hidden;
}
</style>
手写在vue3中的响应式store
window._store 并不是响应式的,如果在 Vue 项目中直接使用,那么就无法自动更新页面 所以我们需要用 ref 和 reactive 去把数据包裹成响应式数据 并且提供统一的操作方法,这其实就是数据管理框架 Vuex 的雏形了
import { inject, reactive } from 'vue'
const STORE_KEY = '__store__'
function useStore() {
return inject(STORE_KEY)
}
function createStore(options) {
return new Store(options)
}
class Store {
constructor(options) {
this.$options = options
this._state = reactive({
data: options.state()
})
this._mutations = options.mutations
}
get state() {
return this._state.data
}
commit = (type, payload) => {
const entry = this._mutations[type]
entry && entry(this.state, payload)
}
install(app) {
app.provide(STORE_KEY, this)
}
}
export { createStore, useStore }
手写在vue3中的vue-router
myRouter.js
import {ref,inject} from 'vue'
const ROUTER_KEY = '__router__'
function createRouter(options){
return new Router(options)
}
function useRouter(){
return inject(ROUTER_KEY)
}
function createWebHashHistory(){
function bindEvents(fn){
window.addEventListener('hashchange',fn)
}
return {
bindEvents,
url:window.location.hash.slice(1) || '/'
}
}
class Router{
constructor(options){
this.history = options.history
this.routes = options.routes
this.current = ref(this.history.url)
this.history.bindEvents(()=>{
this.current.value = window.location.hash.slice(1)
})
}
install(app){
app.provide(ROUTER_KEY,this)
}
}
export {createRouter,createWebHashHistory,useRouter}
main.js
import {
createRouter,
createWebHashHistory,
} from './grouter/index'
const router = createRouter({
history: createWebHashHistory(),
routes // 页面组件SFC组件在对象数据里
})
手写router-view组件
current 返回当前的路由地址,并且使用 ref 包裹成响应式的数据。router-view 组件的功能,就是 current 发生变化的时候,去匹配 current 地址对应的组件,然后动态渲染到 router-view 就可以了
<template>
<component :is="comp"></component>
</template>
<script setup>
import {computed } from 'vue'
import { useRouter } from '../myRouter/index'
let router = useRouter()
const comp = computed(()=>{
const route = router.routes.find(
(route) => route.path === router.current.value
)
return route?route.component : null
})
</script>
手写router-link组件
把 a 标签的 href 属性前面加了个一个 #, 就实现了 hash 的修改
<template>
<a :href="'#'+props.to">
<slot />
</a>
</template>
<script setup>
import {defineProps} from 'vue'
let props = defineProps({
to:{type:String,required:true}
})
</script>
全局挂载组件router-view router-link
const app = createApp()
app.component("router-link",RouterLink)
app.component("router-view",RouterView)
因为这个两个全局组件只有挂载router插件才需要 因此可以放在router插件的install里
import {ref,inject} from 'vue'
import RouterLink from './RouterLink.vue'
import RouterView from './RouterView.vue'
class Router{
....
install(app){
app.provide(ROUTER_KEY,this)
app.component("router-link",RouterLink)
app.component("router-view",RouterView)
}
}
vue3 ts限制
import { computed, reactive, ref } from '@vue/runtime-core';
interface typeObj {
name:string,
price:number
}
const msg = ref('') // 根据输入参数推导字符串类型
const msg1 = ref<string>('') // 可以通过范型显示约束
const obj = reactive({})
const course = reactive<typeObj>({name: '玩转Vue3全家桶', price: 129})
const msg2 = computed(() => '') // 默认参数推导
const course2 = computed<typeObj>(() => {
return {name: '玩转Vue3全家桶', price: 129}
})
👆 ref
reactive
computed
都具有类型隐式推导,根据传入和return 自动推导 并且都可以通过泛型(ts用于类型推导的变量)覆盖隐式推导结果,即显式约束
开发过程使用到的响应式数据,对象类型数据最好都做好泛型指定(基础类型靠隐式推导即可
import {ref, Ref} from 'vue'
interface Todo{
title: string,
done: boolean
}
// 定义响应式对象数据
const item = reactive<Todo>({title: '玩转Vue3全家桶', done: false})
// 定义响应式数组数据
let todos:Ref<Todo[]> = ref([{title:'学习Vue',done:false}])
// 定义响应式
组件开发
组件除了要导出组件自身还要导出类型 在vue的时候是直接引用.vue文件,而.vue文件的js部分就是 export default
在vue3中如果不使用setup script
而是使用 setup function
的话,还是要 export default defineComponent
// 导出组件
export default defineComponent({
name: 'MyComponent',
props: {
list: {
type: Array as PropType<MyComponentProps[]>,
required: true
}
}
})
// 导出类型定义(数组的对象属性类型
export interface MyComponentProps {
id: string,
title: string
}
使用方
// 引入组件及类型
import MyComponent {MyComponentProps} from './MyComponent.vue'
const list: MyComponentProps[] = [
{id:'', title:''}
]