Vue.js 3.0
javaScript 相关
Symbol() 是 JavaScript 中的一个原始数据类型,用于创建唯一的、不可变的值。
安装npm install @vue/cli -g
本地node安装vue 工具用于创建项目
命令行输入 vue ui ,根据网址在线创建
基础语法
语法
1、取值双大括号 {{}}
生命周期
组合式API
创建应用
import { createApp } from ‘vue’
import { createApp } from “vue”; import App from “./App.vue”; const app = createApp(App); 挂载 其他组件 router 、store app.use(router).use(store); //最后 app.mount(“#el”);
.mount() 方法应该始终在整个应用配置和资源注册完成后被调用。同时请注意,不同于其他资源注册方法,它的返回值是根组件实例而非应用实例。
数据和方法
function/箭头函数
使用箭头函数定义方法时并不会创建函数作用域,因此this也不会指向其父级实例,此时的this会向上追踪。当找到某个函数作用域时,this将指向该函数的父级实例;否则,this将指向浏览器的内置对象Windows。
watch更注重于处理数据变化时的业务逻辑,而computed更注重于衍生数据
DOM渲染
选项优先级
选项的优先级 el、template、render三个选项的功能是一致的——获取实例模板(指定或是创建)。然而,当实例同时存在这三个选项时,Vue将如何处理呢? render>template>el
封装复用
计算属性
创建一个只读的计算属性 ref:
const count = ref(1)
const plusOne = computed(() => count.value + 1)
console.log(plusOne.value) // 2
plusOne.value++ // 错误
创建一个可写的计算属性 ref:
const count = ref(1)
const plusOne = computed({
get: () => count.value + 1,
set: (val) => {
count.value = val - 1
}
})
plusOne.value = 1
console.log(count.value) // 0
侦听器
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
watch 的第一个参数可以是不同形式的“数据源”:它可以是一个 ref (包括计算属性)、一个响应式对象、一个 getter 函数、或多个数据源组成的数组
你不能直接侦听响应式对象的属性值 ,因为属性值不为响应式监听,使用getter函数方式可以
const x = ref(0)
const y = ref(0)
// 单个 ref
watch(x, (newX) => {
console.log(x is ${newX}
)
})
// getter 函数
watch(
() => x.value + y.value,
(sum) => {
console.log(sum of x + y is: ${sum}
)
}
)
// 多个来源组成的数组
watch([x, () => y.value], ([newX, newY]) => {
console.log(x is ${newX} and y is ${newY}
)
})
watch 只追踪明确侦听的数据源。它不会追踪任何在回调中访问到的东西。另外,仅在数据源确实改变时才会触发回调。watch 会避免在发生副作用时追踪依赖,因此,我们能更加精确地控制回调函数的触发时机。
watchEffect,则会在副作用发生期间追踪依赖。它会在同步执行过程中,自动追踪所有能访问到的响应式属性。这更方便,而且代码往往更简洁,但有时其响应性依赖关系会不那么明确
watchEffect
对于这种只有一个依赖项的例子来说,watchEffect() 的好处相对较小。但是对于有多个依赖项的侦听器来说,使用 watchEffect() 可以消除手动维护依赖列表的负担。此外,如果你需要侦听一个嵌套数据结构中的几个属性,watchEffect() 可能会比深度侦听器更有效,因为它将只跟踪回调中被使用到的属性,而不是递归地跟踪所有的属性。
const todoId = ref(1)
const data = ref(null)
watch(todoId, async () => {
const response = await fetch(
https://jsonplaceholder.typicode.com/todos/${todoId.value}
)
data.value = await response.json()
}, { immediate: true })
简化
watchEffect(async () => {
const response = await fetch(
https://jsonplaceholder.typicode.com/todos/${todoId.value}
)
data.value = await response.json()
})
修饰符
.lazy
默认情况下,v-model 会在每次 input 事件后更新数据 (IME 拼字阶段的状态例外)。你可以添加 lazy 修饰符来改为在每次 change 事件后更新数据
.number
如果你想让用户输入自动转换为数字,你可以在 v-model 后添加 .number 修饰符来管理输入
如果该值无法被 parseFloat() 处理,那么将返回原始值。
number 修饰符会在输入框有 type=“number” 时自动启用。
.trim
如果你想要默认自动去除用户输入内容中两端的空格,你可以在 v-model 后添加 .trim 修饰符:
.prevent
.prevent修饰符可以用于阻止元素的默认行为,例如防止表单提交或链接页面跳转时重新加载页面。
当使用@click.prevent修饰符时,点击事件将不会触发默认行为(例如将链接打开或提交表单),而只会执行绑定的方法。
点击按钮将调用submitForm方法来提交表单,而不会触发按钮的默认行为,即重新加载页面
事件修饰符 .passive、.capture 和 .once
Prop 逐级透传问题,定义全局参数
provide 和 inject 可以帮助我们解决这一问题。 [1] 一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多深,都可以注入由父组件提供给整条链路的依赖。
父组件提供者 提供key,value
,提供的响应式状态使后代组件可以由此和提供者建立响应式的联系。
import { ref, provide } from ‘vue’
const count = ref(0)
provide(‘key’, count)
Provide除了在一个组件中提供依赖,我们还可以在整个应用层面提供依赖:
import { createApp } from ‘vue’
const app = createApp({})
app.provide(/* 注入名 / ‘message’, / 值 */ ‘hello!’)
在应用级别提供的数据在该应用内的所有组件中都可以注入。这在你编写插件时会特别有用,因为插件一般都不会使用组件形式来提供值。
import { inject } from ‘vue’
const message = inject(‘message’,‘这是默认值’)
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
有的时候,我们可能需要在注入方组件中更改数据。在这种情况下,我们推荐在供给方组件内声明并提供一个更改数据的方法函数
响应性
import { toRefs, toRef } from ‘vue’
export default {
setup(props) {
// 将 props
转为一个其中全是 ref 的对象,然后解构
const { title } = toRefs(props)
// title
是一个追踪着 props.title
的 ref
console.log(title.value)
// 或者,将 `props` 的单个属性转为一个 ref
const title = toRef(props, 'title')
}
}
深层响应性
reactive
import { reactive } from ‘vue’
const obj = reactive({
nested: { count: 0 },
arr: [‘foo’, ‘bar’]
})
function mutateDeeply() {
// 以下都会按照期望工作
obj.nested.count++
obj.arr.push(‘baz’)
}
-
shallowReactive()是 reactive() 的浅层作用形式
-
reactive() 返回的是一个原始对象的 Proxy,它和原始对象是不相等的
-
reactive() API 有两条限制:
仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的 原始类型 无效。
因为 Vue 的响应式系统是通过属性访问进行追踪的,因此我们必须始终保持对该响应式对象的相同引用。这意味着我们不可以随意地“替换”一个响应式对象,因为这将导致对初始引用的响应性连接丢失:
-
reactive() 的种种限制归根结底是因为 JavaScript 没有可以作用于所有值类型的 “引用” 机制。
-
TIP 不推荐使用 reactive() 的泛型参数,因为处理了深层次 ref 解包的返回值与泛型参数的类型不同。
toRefs()
- 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。每个单独的 ref 都是使用 toRef() 创建的。
toRef()
- 基于响应式对象上的一个属性,创建一个对应的 ref。这样创建的 ref 与其源属性保持同步:改变源属性的值将更新 ref 的值,反之亦然
ref
Vue 提供了一个 ref() 方法来
允许我们创建可以使用
任何值类型的响应式 ref
- import { ref } from ‘vue’
const count = ref(0)
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
和响应式对象的属性类似,ref 的 .value 属性也是响应式的。同时,当值为对象类型时,会用 reactive() 自动转换它的 .value
- const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
- ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。这个功能很重要,因为它经常用于将逻辑提取到 组合函数 中
ef 在模板中的解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value
-
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样
-
只有当嵌套在一个深层响应式对象内时,才会发生 ref 解包。当其作为浅层响应式对象的属性被访问时不会解包。
toRow
const foo = {}
const reactiveFoo = reactive(foo)
console.log(toRaw(reactiveFoo) === foo) // true
- 这是一个可以用于临时读取而不引起代理访问/跟踪开销,或是写入而不触发更改的特殊方法。不建议保存对原始对象的持久引用,请谨慎使用
单文件组件
1、组件名应该采用单词大写开头的驼峰命名法,例如"TodoList"。
2、组件应该在一个单独的文件中进行定义,文件名应该与组件名相同,并以.vue为后缀。
当不使用构建步骤时,一个 Vue 组件以一个包含 Vue 特定选项的 JavaScript 对象来定义:
import { ref } from ‘vue’
export default {
setup() {
const count = ref(0)
return { count }
},
template: <button @click="count++"> You clicked me {{ count }} times. </button>
// 或者 template: '#my-template-element'
}
当使用构建步骤时,我们一般会将 Vue 组件定义在一个单独的 .vue 文件中,这被叫做单文件组件 (简称 SFC):
defineProps 是一个仅
const props = defineProps([‘title’])
console.log(props.title)
传递 props,
接收任意类型的 JavaScript 值作为 props
如果你没有使用
export default {
props: [‘title’],
setup(props) {
console.log(props.title)
}
}
defineProps() 和 defineEmits()
const props = defineProps({
foo: String
})
const emit = defineEmits([‘change’, ‘delete’])
// setup 代码
<BlogPost
…
@enlarge-text=“postFontSize += 0.1”
/>
子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件:
vue
{{ title }}
defineEmits 仅可用于
- export default defineComponent({
emits: [‘update:value’],
props: {
value: {
type: String,
required: true
}
},
setup(props, { emit }) {
function onInput(event) {
emit(‘update:value’, event.target.value);
}
return {
onInput
};
}
});
插槽,组件要接收模板内容
为子组件传递一些模板片段,让子组件在它们的组件中渲染这些片段。
实例
Click me!
*
渲染作用域
插槽内容可以访问到父组件的数据作用域,因为插槽内容本身是在父组件模板中定义的
插槽内容无法访问子组件的数据。Vue 模板中的表达式只能访问其定义时所处的作用域,这和 JavaScript 的词法作用域规则是一致的。换言之:
父组件模板中的表达式只能访问父组件的作用域;子组件模板中的表达式只能访问子组件的作用域。
默认内容
Submit
具名插槽
插槽带有name名称
- 使用时指定,v-slot 有对应的简写 #
<!-- header 插槽的内容放这里 -->
* <BaseLayout>
<template #header>
Here might be a page title
<template #default>
A paragraph for the main content.
And another one.
<template #footer>
Here’s some contact info
* <BaseLayout>
<template #header>
Here might be a page title
A paragraph for the main content.
And another one.
<template #footer>
Here’s some contact info
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes:
template
路由 vue-router
安装 npm install vue-router --save
Vue-Router有两种模式:hash模式和history模式。默认的路由模式是hash模式。
import { createRouter, createWebHistory } from ‘vue-router’ const routes = [ { path: ‘/userinfo’, name: ‘UserInfo’, redirect: “/home”, component: () => import(‘…/views/UserInfo.vue’), meta: { name: “why”, age: 18, height: 1.88 }, props: true }] const router = createRouter({ history: createWebHistory(process.env.BASE_URL), routes }) export default router
import { createRouter, createWebHashHistory } from ‘vue-router’ const routes = [{ path: ‘/userinfo’, name: ‘UserInfo’, component: () => import(‘…/views/UserInfo.vue’) }] const router = createRouter({ history: createWebHashHistory(), routes }) export default router
在你的组件中使用 $route 会与路由紧密耦合,这限制了组件的灵活性,因为它只能用于特定的 URL。虽然这不一定是件坏事,但我们可以通过 props 配置来解除这种行为 当 props 设置为 true 时,route.params 将被设置为组件的 props 对于有命名视图的路由,你必须为每个命名视图定义 props components: { default: User, sidebar: Sidebar }, props: { default: true, sidebar: false }
app.use(router)
核心概念
导航守卫
全局前置
onst router = createRouter({ … }) router.beforeEach((to, from) => { // … // 返回 false 以取消导航 return false })
-
可用于登录验证
- /** * 返回值问题: * 1.false: 不进行导航 * 2.undefined或者不写返回值: 进行默认导航 * 3.字符串: 路径, 跳转到对应的路径中 * 4.对象: 类似于 router.push({path: “/login”, query: …}) */ router.beforeEach((to, from) => { console.log(
进行了${++counter}路由跳转
) // if (to.path.indexOf(“/home”) !== -1) { // return “/login” // } if (to.path !== “/login”) { const token = window.localStorage.getItem(“token”); if (!token) { return “/login” } }
- /** * 返回值问题: * 1.false: 不进行导航 * 2.undefined或者不写返回值: 进行默认导航 * 3.字符串: 路径, 跳转到对应的路径中 * 4.对象: 类似于 router.push({path: “/login”, query: …}) */ router.beforeEach((to, from) => { console.log(
路由独享的守卫
const routes = [ { path: ‘/users/:id’, component: UserDetails, beforeEnter: (to, from) => { // reject the navigation return false }, }, ]
组件内的守卫
动态路由
// 添加路由
router.addRoute({ path: ‘/about’, component: About }) 添加后不会调转,需要调用调转 push,replace
// 我们也可以使用 this.$route
或 route = useRoute() (在 setup 中) router.replace(router.currentRoute.value.fullPath)
// 删除路由
router.removeRoute(‘about’) router.hasRoute():检查路由是否存在。
router.getRoutes():获取一个包含所有路由记录的数组。
父子路由,在子路由前不加‘/’,编译器会自动添加
命名视图
一个视图使用一个组件渲染,因此对于同个路由,多个视图就需要多个组件。确保正确使用 components 配置 (带上 s): const router = createRouter({ history: createWebHashHistory(), routes: [ { path: ‘/’, components: { default: Home, // LeftSidebar: LeftSidebar 的缩写 LeftSidebar, // 它们与 <router-view>
上的 name
属性匹配 RightSidebar, }, }, ], })
使用
push# 通过在历史堆栈中推送一个 entry,以编程方式导航到一个新的 URL。 this. r o u t e r . p u s h ( ‘路由地址’ ) t h i s . router.push(‘路由地址’) this. router.push(‘路由地址’)this.router.push({ name:‘about’, params:{ id:1 } }) Home Home Home Home User Register 命名视图
返回历史中的路由,前一页后一页 go(delta: number): void 允许你在历史中前进或后退。 this.$router.go() forward() == go(1) const router= useRouter() //向前移动一个记录,与router.forward()相同 router.go(1) router.forward() //返回一条记录,与router.back()相同 router.go(-1) router.back()
获取 链接参数 this. r o u t e . p a r a m s t h i s . route.params this. route.params this.route.query
父子路由
export default new VueRouter({
routes:[
{
//第一组路由规则
path:'/home',
component:Home
children:[//为什么是数组形式?因为可能有多个子路由
{
path:'news',//这里的路径就不要加'/'了,因为底层给你遍历这一堆规则的时候人家已经给你加上'/'了
component:News,
}
]
},
]
})
反例
-
News
Message
- 点击news路径变成了:
http://localhost:8080/#/news
1
为什么?
因为没有对应规则;刚才的路径/home变成了/news是一种路径的改变
路径改变了,前端路由器就监测到了,然后路由器拿着你的/news回到路由规则(router > index.js)里面进行匹配(在routes里面问)对比完毕没有对的上的,所以页面什么都不成现
正例
-
News
Message
- 子主题1
状态管理 vuex&&Pinia
Pinpa
安装 npm install pinia --save
包名 createPinia
import { createPinia } from “pinia”; const pinia = createPinia(); app.use(pinia);
核心
包名 defineStore
import { defineStore} from ‘pinia’
state、actions 与 getters
const userStore = defineStore(‘user’,{ state: () => ({ count: 0 }), getters: { doubleCount: (state) => state.count * 2, }, actions: { increment() { this.count++ }, async randomizeCounter() { this.userData = await api.post({ login, password }) }, }, })
-
Getter 完全等同于 store 的 state 的计算值。可以通过 defineStore() 中的 getters 属性来定义它们。推荐使用箭头函数,并且它将接收 state 作为第一个参数
-
在使用常规函数定义 getter 时,我们也可以通过 this 访问到整个 store 实例,但(在 TypeScript 中)必须定义返回类型。这是为了避免 TypeScript 的已知缺陷,不过这不影响用箭头函数定义的 getter,也不会影响不使用 this 的 getter。
-
Action 相当于组件中的 method。它们可以通过 defineStore() 中的 actions 属性来定义,并且它们也是定义业务逻辑的完美选择。也可通过 this 访问整个 store 实例
-
不同的是,action 可以是异步的,你可以在它们里面 await 调用任何 API,以及其他 action!
使用
import { useUsersStore } from “…/src/store/user”; const store = useUsersStore(); console.log(store);
- 也可以store中使用其他 store
// 重置store const reset = () => { store.$reset(); };
import { storeToRefs } from ‘pinia’; const store = useUsersStore(); const { name, age, sex } = storeToRefs(store);
vuex
安装 npm install vuex@next --save
核心
state、mutations、actions、getters、modules 提供唯一的公共数据源,所有共享的数据统一放到store的state进行储存,相似与data
import { createStore } from ‘vuex’ export default createStore({ state: { info: ‘hello’ }, mutations: { // 定义mutations,用于修改状态(同步) updateInfo (state, payload) { state.info = payload } }, actions: { // 定义actions,用于修改状态(异步) // 2秒后更新状态 updateInfo (context, payload) { setTimeout(() => { context.commit(‘updateInfo’, payload) }, 2000) }, // 利用ES2015 的参数解构来简化代码 incrementAsync ({ commit }) { setTimeout(() => { commit(‘increment’) }, 1000) }, async actionB ({ dispatch, commit }) { await dispatch(‘actionA’) // 等待 actionA 完成 commit(‘gotOtherData’, await getOtherData()) } }, getters: { // 定义一个getters,有两个参数 formatInfo (state,getters) { return state.info + ’ Tom’ } }, modules: { } })
-
getters
- Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性) mapGetters 辅助函数仅仅是将 store 中的 getter 映射到局部计算属性: import { mapGetters } from ‘vuex’
-
mutations
-
Vuex 的 store 中的状态的唯一方法是提交 mutation,Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。它会接受 state 作为第一个参数
-
一条重要的原则就是要记住 mutation 必须是同步函数
-
-
actions
-
Action 类似于 mutation,不同在于: Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作。
-
Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters
-
ES2015 的参数解构来简化代码(特别是我们需要调用 commit 很多次的时候)
-
使用
store.commit(‘increment’) store.commit(‘increment’, 10) 或 store.commit({ type: ‘increment’, amount: 10 })
import { mapMutations } from ‘vuex’
Action 通过 store.dispatch 方法触发: store.dispatch(‘increment’) // 以载荷形式分发 store.dispatch(‘incrementAsync’, { amount: 10 }) // 以对象形式分发 store.dispatch({ type: ‘incrementAsync’, amount: 10 }) store.dispatch(‘actionA’).then(() => { })
用于在浏览器中
发起网络请求的工具
Axios是一个基于Promise的HTTP客户端,可以用于发送HTTP请求并处理响应。它支持更多的浏览器,包括IE8及以上版本,并提供了更多的功能,如请求取消、拦截请求和响应等。同时,在错误处理和配置方面,Axios也比Fetch更加全面和灵活。
axios.get(‘https://api.example.com/data’)
.then(response => console.log(response.data))
.catch(error => console.error(error));
axios.post(‘https://api.example.com/login’, {
username: ‘example’,
password: ‘password’
})
.then(response => console.log(response.data))
.catch(error => console.error(error));
Fetch是Web API,是浏览器内置的API之一,使用Promise对象来处理异步请求和响应。Fetch支持ES6语法特性,并且比较轻量级,不需要任何第三方库或插件。
fetch(‘https://api.example.com/data’)
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error(error));
响应式中与vue2的区别
机制不同
vue2中的代理方式为Object.defineProperty(obj, ‘title’, {
get() {},
set() {},
})
当项目里“读取 obj.title”和“修改 obj.title”的时候被 defineProperty 拦截,但 defineProperty 对不存在的属性无法拦截,所以 Vue 2 中所有数据必须要在 data 里声明。
vue3采用 Proxy 这个 API 就是真正的代理
new Proxy(obj, {
get() { },
set() { },
})
需要注意的是,虽然 Proxy 拦截 obj 这个数据,但 obj 具体是什么属性,Proxy 则不关心,统一都拦截了。而且 Proxy 还可以监听更多的数据格式,比如 Set、Map,这是 Vue 2 做不到的。