1. node 环境 > 16
2. 给 VS code 安装 Volar 扩展插件
3. 整个应用程序的app暴露出一个全局配置选项
const app = createApp(App)
// 应用实例会暴露一个 .config 对象允许我们配置一些应用级的选项
app.config.errorHandler = (err)=> {
console.log("程序的错误信息。。。")
console.log(err)
}
app.mount("#app")
4. 整个应用程序的app暴露出一个全局组件注册方法
const app = createApp(App)
// 应用实例还提供了一些方法来注册应用范围内可用的资源,例如注册一个组件:
// app.component('全局组件名称', 一个全局组件)
app.mount("#app")
5. 给整个应用程序添加全局数据
// 添加全局数据
app.config.globalProperties = {
sx: 777
}
// 接下来在组件的模板中可直接获取使用
{{sx}}
// 在组件脚本中, 需要先获取全局实例对象
6. 创建一个响应式的 对象 或 数组
// 注意: reactive() 返回的是一个原始对象的 Proxy
const raw = {}
const proxy = reactive(raw)
只有代理对象是响应式的,更改原始对象不会触发更新。
为保证访问代理的一致性,
对同一个原始对象调用 reactive() 会总是返回同样的代理对象,
而对一个已存在的代理对象调用 reactive() 会返回其本身:
// 在同一个对象上调用 reactive() 会返回相同的代理
console.log(reactive(raw) === proxy) // true
// 在一个代理上调用 reactive() 会返回它自己
console.log(reactive(proxy) === proxy) // true
这个规则对嵌套对象也适用。依靠深层响应性,响应式对象内的嵌套对象依然是代理:
const proxy = reactive({})
const raw = {}
proxy.nested = raw
console.log(proxy.nested === raw) // false
因为 Vue 的响应式系统是通过属性访问进行追踪的,
因此我们必须始终保持对该响应式对象的相同引用。
这意味着我们不可以随意地“替换”一个响应式对象,
因为这将导致对初始引用的响应性连接丢失:
let state = reactive({ count: 0 })
// 上面的引用 ({ count: 0 }) 将不再被追踪(响应性连接已丢失!)
state = reactive({ count: 1 })
// n 是一个局部变量,同 state.count
// 失去响应性连接
let n = state.count
// 不影响原始的 state
n++
// count 也和 state.count 失去了响应性连接
let { count } = state
// 不会影响原始的 state
count++
// 该函数接收一个普通数字,并且
// 将无法跟踪 state.count 的变化
callSomeFunction(state.count)
// 组合式 api
// 选项式 api
7. nextTick() 等 编程了 全局api
import { nextTick } from 'vue'
8. 深层响应式对象 & 浅层响应式对象
9. 响应式变量
Vue 提供了一个 ref() 方法来允许我们创建可以使用任何值类型的响应式 ref:
import { ref } from 'vue'
const count = ref(0)
ref() 将传入参数的值包装为一个带 .value 属性的 ref 对象
当值为对象类型时,会用 reactive() 自动转换它的 .value。
const objectRef = ref({ count: 0 })
// 这是响应式的替换
objectRef.value = { count: 1 }
ref 被传递给函数或是从一般对象上被解构时,不会丢失响应性
const obj = {
foo: ref(1),
bar: ref(2)
}
// 该函数接收一个 ref
// 需要通过 .value 取值
// 但它会保持响应性
callSomeFunction(obj.foo)
// 仍然是响应式的
const { foo, bar } = obj
简言之,ref() 让我们能创造一种对任意值的 “引用”,并能够在不丢失响应性的前提下传递这些引用。
ref的解包
ref在模板中解包
当 ref 在模板中作为顶层属性被访问时,它们会被自动“解包”,所以不需要使用 .value
function increment() {
count.value++ // 需要.value
}
ref 在响应式对象中的解包
当一个 ref 被嵌套在一个响应式对象中,作为属性被访问或更改时,它会自动解包,因此会表现得和一般的属性一样
数组和集合类型的 ref 解包
跟响应式对象不同,当 ref 作为响应式数组或像 Map 这种原生集合类型的元素被访问时,不会进行解包
const books = reactive([ref('Vue 3 Guide')])
// 这里需要 .value
console.log(books[0].value)
const map = reactive(new Map([['count', ref(0)]]))
// 这里需要 .value
console.log(map.get('count').value)
10. 计算属性
{{ xxx }}
computed()
方法期望接收一个 getter
函数,返回值为一个计算属性 ref
。和其他一般的 ref 类似,你可以通过 xxx.value
访问计算结果。计算属性 ref 也会在模板中自动解包,因此在模板表达式中引用时无需添加 .value
11. 计算属性 & 方法
计算属性值会基于其响应式依赖被缓存
一个计算属性仅会在其响应式依赖更新时才重新计算。这意味着只要 依赖的响应式数据
不改变,无论多少次访问 计算属性
都会立即返回先前的计算结果,而不用重复执行 getter 函数。
方法调用总是会在重渲染发生时再次执行函数。
像下面的计算属性永远不会更新, 因为 Date.now()
并不是一个响应式依赖:
const now = computed(() => Date.now())
12. 计算属性为什么要缓存
想象一下我们有一个非常耗性能的计算属性 list
,需要循环一个巨大的数组并做许多计算逻辑,并且可能也有其他计算属性依赖于 list
。没有缓存的话,我们会重复执行非常多次 list
的计算函数,然而这实际上没有必要!如果你确定不需要缓存,那么也可以使用方法调用。
13. 可写计算属性
只在某些特殊场景中你可能才需要用到“可写”的属性,你可以通过同时提供 getter 和 setter 来创建:
现在当你再运行 fullName.value = 'John Doe'
时,setter 会被调用而 firstName
和 lastName
会随之更新。
另注意:
在计算属性中使用 reverse()
和 sort()
的时候务必小心!这两个方法将变更原始数组,计算函数中不应该这么做。请在调用这些方法之前创建一个原数组的副本:
- return 数组.reverse()
+ return [...数组].reverse()
14. 数组变化侦测
变更方法
Vue 能够侦听响应式数组的变更方法,并在它们被调用时触发相关的更新。
变更: 就是改变数组内的元素, 但不会生成新的数组
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
替换一个数组
例如 filter()
,concat()
和 slice()
等,这些方法都不会更改原数组,而总是返回一个新数组。
在这种情况下, 我们需要用新生成的数组 来 替换 老的数组
比如 :item 是一个数组的 ref
那么: 可以这样来操作
item.value = item.value.concat(...)
其本质就是 , 保证 ref 的引用不变, 还是响应式的
你可能认为这将导致 Vue 丢弃现有的 DOM 并重新渲染整个列表——幸运的是,情况并非如此。Vue 实现了一些巧妙的方法来最大化对 DOM 元素的重用,因此用另一个包含部分重叠对象的数组来做替换,仍会是一种非常高效的操作。
15. 在点击事件中 访问事件对象 event
function warn(message, event) {
// 这里可以访问原生事件
if (event) {
event.preventDefault()
}
alert(message)
}
16. 生命周期
17. 监听器 watch() 和 watchEffect()
watch 函数用来侦听特定的数据源,并在回调函数中执行副作用。
默认情况是惰性
的,也就是说仅在侦听的源数据变更时才执行回调。
你不能直接侦听响应式对象的属性值
const obj = reactive({ count: 0 })
// 错误,因为 watch() 得到的参数是一个 number
watch(obj.count, (count) => {
console.log(`count is: ${count}`)
})
这里需要用一个返回该属性的 getter 函数
// 提供一个 getter 函数
watch(
() => obj.count,
(count) => {
console.log(`count is: ${count}`)
}
)
简单来说,
侦听reactive
数据
const state = reactive({ nickname: "xiaofan", age: 20 });
// 修改age值时会触发 watch的回调
watch(
() => state.age,
(curAge, preAge) => {
console.log("新值:", curAge, "老值:", preAge);
}
);
侦听ref
数据
const year = ref(0);
watch(year, (newVal, oldVal) => {
console.log("新值:", newVal, "老值:", oldVal);
});
同时侦听多个数据
// 当我们需要侦听多个数据源时, 可以进行合并, 同时侦听多个数据:
watch([() => state.age, year], ([curAge, newVal], [preAge, oldVal]) => {
console.log("新值:", curAge, "老值:", preAge); console.log("新值:", newVal,
"老值:", oldVal); });
侦听复杂的嵌套对象
const state = reactive({
room: {
id: 100,
attrs: {
size: "140平方米",
type: "三室两厅",
},
},
});
// 注意在此使用的 watch() 方法中的第三个参数: deep:true
watch(
() => state.room,
(newType, oldType) => {
console.log("新值:", newType, "老值:", oldType);
},
{ deep: true }
);
// 补充: 默认情况下,watch 是惰性的, 那什么情况下不是惰性的, 可以立即执行回调函数呢?
// 其实使用也很简单, 给第三个参数中设置immediate: true 即可。
stop 停止监听
组件中创建的watch监听,会在组件被销毁时自动停止
如果在组件销毁之前我们想要停止掉某个监听, 可以调用watch()函数的返回值
const xxx = watch(.....);
setTimeout(()=>{
// 停止监听
xxx()
}, 3000)
和 watchEffect() 对比
// watch已经能满足监听的需求,为什么还要有watchEffect呢
// 还是观察对比, 找区别吧
watchEffect(
() => {
console.log(数据1);
console.log(数据2);
}
);
上方代码执行结果如下:
执行结果首先打印一次数据1
和数据2
的值;
然后每隔一秒,再打印数据1
和数据2
的值
从上面的代码可以看出, watchEffect
并没有像 watch
一样需要先传入依赖,
watchEffect
会自动收集依赖, 只要指定一个回调函数即可。
在组件初始化时, 会先执行一次来收集依赖,
然后当收集到的依赖中数据发生变化时, 就会再次执行回调函数。
所以总结对比如下:
-
watchEffect
不需要手动传入依赖 -
watchEffect
会先执行一次用来自动收集依赖 -
watchEffect
无法获取到变化前的值, 只能获取变化后的值
18. 事件
在组件的模板表达式中,可以直接使用 $emit
方法触发自定义事件
可以给事件带参数
$emit
方法不能在组件的 部分中
可以显式地通过 defineEmits()
宏来声明
defiineEmits()
宏函数使用的注意事项
defineEmits()
宏不能在子函数中使用, 它必须直接放置在的顶级作用域下
-
如果你在选项式api组件中,显式地使用了
setup
函数, 而不是,则事件需要通过
emits
选项来定义,emit
函数也被暴露在setup()
的上下文对象上export default { emits: ['inFocus', 'submit'], setup(props, ctx) { ctx.emit('submit') } }
-
这个
emits
选项还支持对象语法,它允许我们对触发事件的参数进行验证: -
如果你正在搭配 TypeScript 使用
,也可以使用纯类型标注来声明触发的事件:
19. 事件 配合 v-model 来完成 组件内的 input 输入框数据绑定
事件 配合 v-model 来完成 组件内的 input 输入框数据绑定
<组件
:modelValue="searchText"
@update:modelValue="newValue => searchText = newValue"
/>
20. getter
setter
配合 v-model 来完成 组件内的 input 输入框数据绑定
21. 依赖注入(vue3, 所以只能出现在 setup 中)
- 数据提供者
// 在组件内使用
import { provide } from 'vue'
provide(名称 , 值)
// 给整个app使用
app.provide(名称 , 值)
// 值 也可以是 响应式的值,
// 使后代组件可以由此和提供者建立响应式的联系
// 一个组件可以多次调用 provide(),
// 使用不同的注入名,注入不同的依赖值。
// 给值套上 readonly, 来不让注入方修改值
app.provide(名称 , readonly(值))
- 数组注入
// 使用 inject() 函数
const aaa = inject('名称')
// 没有提供者的时候, 可以使用默认值
// 如果没有祖先组件提供 "message"
// `value` 会是 "这是默认值"
const value = inject('名称', '这是默认值')
// 默认值 也可以是一个工厂函数
const value = inject('名称', ()=>{。。。。})
22. 异步组件加载
// 异步组件 的 局部注册
import { defineAsyncComponent } from 'vue'
const 异步组件 = defineAsyncComponent(() => {
// 情况1:
return new Promise((resolve, reject) => {
// ...从服务器获取组件
resolve(/* 获取到的组件 */)
})
// 情况2:
return import('./组件.vue')
})
// 异步组件 的 全局注册
app.component('异步组件', defineAsyncComponent(() =>
import('./组件.vue')
))
// 附加属性或功能
const AsyncComp = defineAsyncComponent({
// 加载函数
loader: () => import('./Foo.vue'),
// 加载异步组件时使用的组件
loadingComponent: LoadingComponent,
// 展示加载组件前的延迟时间,默认为 200ms
delay: 200,
// 加载失败后展示的组件
errorComponent: ErrorComponent,
// 如果提供了一个 timeout 时间限制,并超时了
// 也会显示这里配置的报错组件,默认值是:Infinity
timeout: 3000
})
// ***也可以搭配 组件
23. 内部组件 ---
并可以在等待时渲染一个加载状态。
可以等待的异步依赖有两种
-
带有异步
setup()
钩子的组件。这也包含了使用时有顶层
await
表达式的组件。export default { async setup() { const res = await fetch(...) const posts = await res.json() return { posts } } }
-
异步组件
/* 异步组件默认就是“suspensible”的。 这意味着如果组件关系链上有一个
, 那么这个异步组件就会被当作这个 的一个异步依赖。 在这种情况下,加载状态是由 控制, 而该组件自己的加载、报错、延时和超时等选项都将被忽略。 异步组件也可以通过在选项中指定 suspensible: false 表明不用 Suspense 控制, 并让组件始终自己控制其加载状态。 */ // 在 {{ posts }}
组将用法:
Loading...
组件有两个插槽:#default
和 #fallback
。
#default
和 #fallback
中都只能接受一个根元素
在可能的时候都将显示默认槽中的节点。否则将显示后备槽中的节点。
组件会触发三个事件:pending
、resolve
和 fallback
。
我们常常会将
和
、
、
等组件结合。
要保证这些组件都能正常工作,嵌套的顺序非常重要。
正在加载...
24. ctx 属性
vue3.x 开发环境之下, 可以看到$router
、$store
、声明的变量和方法等
但 在 vue3.x 生产换件之下, 生产环境的ctx
,$router
、$store
没有了,其他属性也都没有了,不能通过ctx.$router
、ctx.$store
访问router和store,因此ctx可以说对我们没有用,应该避免在代码中使用ctx
所以, : 对于网上一些其他文档使用ctx.$router
、ctx.$store
访问router和store的应该小心避坑,注意开发环境和生产环境的差别
25. 关于生命周期中的一些区别:
执行顺序方面:
3中的setup相当于2中的beforeCreate和created的合并。
vue3.x中会先执行`setup`方法,再执行兼容2.x的其他方法,比如`data`、`computed`、`watch`等,
并且在`setup`执行过程中,无法访问`data`中定义的属性,因为此时还未执行到`data`方法
又并且,setup 函数只走一遍
mount挂载的区别:
- 2.x会使用挂载元素的
outerHTML
作为template,并替换挂载元素 - 3.x会使用挂载元素的
innerHTML
作为template,并且只替换挂载元素的子元素
26. 关于元素或组件引用的问题
在2.x
可以在组件挂载之后通过this.$el
访问组件根元素
但3.x
中没有this了, 一切都发生变化了:
3.x
去掉了this,但支持了Fragment,所以this.$el
没有存在的意义,建议通过refs
访问DOM
在组合式 api 中, reactive refs
和 template refs
的概念已经是统一的。为了获得对模板内元素或组件实例的引用,我们可以像往常一样在 setup()
中声明一个 ref
并返回它
在 v-for 中 使用 refs
{{ item }}
27. 自定义指令 directive 的改动
// vue2.x
export default {
name: 'YourDirectiveName',
bind(el, binding, vnode, oldVnode) {},
inserted(...) {},
update(...) {},
componentUpdated(...) {},
unbind(...) {}
}
// vue3.x
export default {
beforeMount(el, binding, vnode, oldVnode) {},
mounted(...) {},
beforeUpdate(...) {},
updated(...) {},
beforeUnmount(...) {},
unmounted() {...}
}
28. 对 render
方法的 改动
vue、react都提供了render
方法渲染html模板,直接使用render
方法的还是比较少,毕竟有template
和JSX
,对于确实需要自定义render
方法渲染模板内容的,具体变动如下:
// vue2.x
export default {
render(h) {
return h('div')
}
}
// vue3.x
import { h } from 'vue'
export default {
render() {
return h('div')
}
}
29. 对事件总线 EventBus
的改动
在Vue2.x中可以通过EventBus
的方法来实现组件通信
// 声明实例
var EventBus = new Vue()
Vue.prototype.$globalBus = EventBus
// 组件内调用
this.$globalBus.$on('my-event-name', callback)
this.$globalBus.$emit('my-event-name', data)
在vue3.x中移除了 $on
、$off
等方法,而是推荐使用mitt
方案来代替:
// 声明实例
import mitt from 'mitt'
const emitter = mitt()
// 组件内调用
// 监听所有事件
emitter.on('*', (type, e) => console.log(type, e))
// 监听个别事件
emitter.on('my-event-name', callback)
emitter.emit('my-event-name', data)
// 清除所有事件
emitter.all.clear()
30. 状态管理---vuex的区别
创建
// Vue2
export default new Vuex.Store({
state: {
count:1
},
mutations: {
inc(state){
state.count ++
}
},
actions: {
},
modules: {
}
})
// Vue3
export default Vuex.createStore({
state: {
count:1
},
mutations: {
add(state){
state.count ++
}
},
actions: {
},
modules: {
}
});
使用
// Vue2
export default {
name: "Home",
data() {
return {
state: this.$store.state
};
},
computed: {
double() {
return this.$store.state.count * 2;
},
},
methods: {
add() {
this.$store.commit("add");
}
}
};
// Vue3
import { computed, reactive } from "vue";
import { useStore } from "vuex";
export default {
setup() {
const store = useStore()
const state = store.state
const double = computed(() => store.state.count * 2)
const add = () => {
store.commit("add");
};
return { state, add ,double};
}
};
31. 在vue3中自定义 hook 函数
类似的, 钩子函数以 use 开头
创建 usePerosn.js 文件
import { computed, reactive } from "vue"
export default function (per = {name: "zhangsan", age:12}) {
const ren = reactive(per)
const changeName = (name) => {
ren.name = name
}
const zengjiaAge = (num) => {
ren.age = ren.age + num
}
const getBirth = computed(()=> {
return 2022 - ren.age
})
return {
ren ,
changeName,
zengjiaAge,
getBirth
}
}
在组件中 使用 该 person 钩子
ren的信息{{JSON.stringify(ren)}}
{{getBirth}}年出生的
32. teleport , 将其所包含的组件的层级结构显示到特定的 地方
// 比如说组件A这样定义
比如说弹框组件