mixin 混入
局部 mixin
- 组件 data 优先级高于 mixin data 优先级。
- 生命周期函数,先执行 mixin 里面的,再执行组件里面的。
- methods 中的函数,如果组件中与 mixin 中的函数命名相同,执行组件中的函数。
const Mixin = {
data() {
return {
count: 12
}
},
created() {
console.log('mixin created');
},
methods: {
handleClick() {
console.log('mixin handleClick');
}
}
}
const app = Vue.createApp({
data() {
return {
message: 'hello world'
}
},
mixins: [Mixin],
methods: {
handleClick() {
console.log('handleClick');
}
},
template: '<div @click="handleClick">{{message}}{{count}}</div>'
});
app.mount('#root');
- 全局mixin
一旦定义,每个组件都会把 mixin 注入进来
不建议使用,维护性不高
const app = Vue.createApp({
data() {
return {
message: 'hello world'
}
},
methods: {
handleClick() {
console.log('handleClick');
}
},
template: '<div @click="handleClick">{{message}}{{count}}</div><counter />'
});
app.component('counter', {
template: '<div>{{count}}</div>'
});
app.mixin({
data() {
return {
count: 12
}
},
created() {
console.log('mixin created');
},
methods: {
handleClick() {
console.log('mixin handleClick');
}
}
});
app.mount('#root');
- 自定义的属性,组件中的属性优先级高于 mixin 属性的优先级
- 自定义属性访问可用this.$options.属性名
const Mixin = {
number: 123
}
const app = Vue.createApp({
number: 456,
mixins: [Mixin],
template: '<div>{{this.$options.number}}</div>'
});
app.mount('#root');
- 修改自定义属性合并策略
app.config.optionMergeStrategies.number = (mixinVal, appVal) => {
return mixinVal || appVal;
}
自定义指令 directive
- 全局指令
const app = Vue.createApp({
template: `<input v-focus />`
});
app.directive('focus', {
mounted(el) {
el.focus();
}
});
app.mount('#root');
- 局部指令
const directives = {
focus: {
mounted(el) {
el.focus();
}
}
};
const app = Vue.createApp({
directives,
template: `<input v-focus />`
});
app.mount('#root');
- 所有自定义指令的生命周期函数
const directives = {
focus: {
beforeMount(el) {
},
mounted(el) {
el.focus();
},
beforeUpdate() {
},
updated() {
},
beforeUnmount() {
},
unmounted() {
}
}
};
自定义指令案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
.box {
position: absolute;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
template: '<div v-pos:left="200" class="box"><input /></div>'
});
// 写法 1 写法 2 和这种写法功能相同
app.directive('pos', (el, binding) => {
el.style[binding.arg] = binding.value + 'px';
});
// 写法 2
app.directive('pos', {
mounted(el, binding) {
el.style[binding.arg] = binding.value + 'px';
},
updated(el, binding) {
el.style[binding.arg] = binding.value + 'px';
}
});
app.mount('#root');
</script>
</body>
</html>
teleport 传送门
将里面包裹的元素传送至对应的元素下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://unpkg.com/vue@next"></script>
<style>
.box {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: red;
width: 200px;
height: 200px;
}
.mask {
position: absolute;
top: 0;
bottom: 0;
right: 0;
left: 0;
background: #000;
opacity: .5;
}
</style>
</head>
<body>
<div id="root"></div>
<script>
const app = Vue.createApp({
// 还可以用 id 传送
template: `
<div class="box">
<teleport to="body">
<div class="mask"></div>
</teleport>
</div>
`
});
app.mount('#root');
</script>
</body>
</html>
render 函数
vue 渲染过程
template -> render -> h -> 虚拟 DOM(JS 对象) -> 真实 DOM -> 展示到页面上
- 内容变量为字符串
const app = Vue.createApp({
template: `<my-title :level="1">hello</my-title>`
});
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
return h('h' + this.level, {}, this.$slots.default());
}
});
app.mount('#root');
- 内容变量为数组
const app = Vue.createApp({
template: `<my-title :level="1">hello</my-title>`
});
app.component('my-title', {
props: ['level'],
render() {
const { h } = Vue;
return h('h' + this.level, {}, [this.$slots.default(), h('h4', {}, 'hello world')]);
}
});
app.mount('#root');
插件的定义和使用
const Plugin = {
install(app, options) {
app.provide('count', 100);
app.config.globalProperties.$sayHello = 'hello world';
}
}
const app = Vue.createApp({
template: `<div><my-title /></div>`
});
app.component('my-title', {
inject: ['count'],
template: `<div>{{count}}</div>`,
mounted() {
console.log(this.$sayHello);
}
});
app.use(Plugin, { name: 'jk' });
app.mount('#root');
插件实例,数据验证
const app = Vue.createApp({
data() {
return {
name: 'twenj',
age: 28
}
},
rules: {
name: {
validate: name => name.length > 4,
message: '姓名长度过短'
},
age: {
validate: age => age > 25,
message: '年龄过小'
}
},
template: `<div>name: {{name}}, age: {{age}}</div>`
});
// 插件实现的另一种方法
const validatePlugin = (app, options) => {
app.mixin({
created() {
const rules = this.$options.rules;
for (const key in rules) {
this.$watch(key, (newVal) => {
if (!rules[key].validate(newVal)) {
console.log(rules[key].message);
}
})
}
}
});
}
app.use(validatePlugin);
const vm = app.mount('#root');
Composition API
setup
它是 Composition API 最核心的东西,它里面不能用 this,他 return 的东西会挂到外部,可以被模版所使用,他不能用实例上的方法,但是实例上的方法可以调用它。
const app = Vue.createApp({
template: `
<div @click="handleClick">{{name}}</div>
`,
methods: {
test() {
console.log(this.$options.setup());
}
},
mounted() {
this.test();
},
// created 实例被完全初始化之前
setup(props, context) {
return {
name: 'zhangsan',
handleClick: () => {
alert(123);
}
};
}
});
const vm = app.mount('#root');
ref, reactive 响应式的引用
原理,通过 proxy 对数据进行封装,当数据变化时,触发模板等内容的更新
ref 处理基础类型的数据
reactive 处理非基础类型的数据
- ref
const app = Vue.createApp({
template: `<div>{{name}}</div>`,
setup() {
const { ref } = Vue;
// proxy, 'jack' 变成 proxy({value: 'jack'}) 这样的一个响应式引用
const name=ref("jack");
setTimeout(() => {
name.value = 'tom';
}, 2000);
return {
name
}
}
});
const vm = app.mount('#root');
- reactive
const app = Vue.createApp({
template: `<div>{{nameObj.name}}</div>`,
setup(props, context) {
const { reactive } = Vue;
// proxy, {name: 'jack'} 变成 proxy({name: 'jack'}) 这样的一个响应式引用
const nameObj = reactive({name: 'jack'});
setTimeout(() => {
nameObj.name = 'tom';
}, 2000);
return {
nameObj
}
}
});
const vm = app.mount('#root');
- readonly
const app = Vue.createApp({
template: `<div>{{nameObj.name}}</div>`,
setup(props, context) {
const { reactive } = Vue;
// proxy, {name: 'jack'} 变成 proxy({name: 'jack'}) 这样的一个响应式引用
const nameObj = reactive({name: 'jack'});
const copyNameObj = readonly(nameObj);
setTimeout(() => {
nameObj.name = 'tom';
copyNameObj.name = 'tom';
}, 2000);
return {
nameObj
}
}
});
const vm = app.mount('#root');
- toRefs
const app = Vue.createApp({
template: `<div>{{name}}</div>`,
setup(props, context) {
const { reactive, toRefs } = Vue;
// proxy, {name: 'jack'} 变成 proxy({name: 'jack'}) 这样的一个响应式引用
const nameObj = reactive({name: 'jack'});
setTimeout(() => {
nameObj.name = 'tom';
}, 2000);
// toRefs 会将 proxy({name: 'jack'}) 变成
// {
// proxy({name: 'jack'})
// }
const { name } = toRefs(nameObj);
return {
name
}
}
});
const vm = app.mount('#root');
toRef 以及 context 参数
- toRef
不建议使用
const app = Vue.createApp({
template: `<div>{{age}}</div>`,
setup(props, context) {
const { reactive, toRef } = Vue;
// proxy, {name: 'jack'} 变成 proxy({name: 'jack'}) 这样的一个响应式引用
const nameObj = reactive({name: 'jack'});
// toRefs 会将 proxy({name: 'jack'}) 变成
// {
// proxy({name: 'jack'})
// }
const age = toRef(nameObj, 'age');
setTimeout(() => {
age.value = 20;
}, 2000);
return {
age
}
}
});
const vm = app.mount('#root');
- context
const app = Vue.createApp({
template: `<child />`,
});
app.component('child', {
template: `<div>child</div>`,
setup(props, context) {
const { attrs, slots, emit } = context;
console.log(attrs);
slots.default();
emit('change');
}
});
Composition API 实例 todolist
const listEffect = () => {
const { reactive } = Vue;
const list = reactive([]);
const addListItem = (item) => {
list.push(item);
}
return { list, addListItem }
}
const app = Vue.createApp({
setup() {
const { ref } = Vue;
let inputValue = ref('');
const { list , addListItem } = listEffect();
const handleSubmit = () => {
addListItem(inputValue.value);
inputValue.value = '';
}
return {
list,
inputValue,
handleSubmit
}
},
template: `
<div>
<input v-model="inputValue">
<button type="button" @click="handleSubmit">提交</button>
</div>
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
`
});
const vm = app.mount('#root');
Composition API 的计算属性
- 只有一个函数
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
function handleClick () {
count.value += 1;
}
const countAddFive = computed(() => {
return count.value + 5;
});
return { count, handleClick, countAddFive };
},
template: `<div @click="handleClick">{{count}} -- {{countAddFive}}</div>`
});
const vm = app.mount('#root');
- 分别有 get 和 set 方法
const app = Vue.createApp({
setup() {
const { ref, computed } = Vue;
const count = ref(0);
function handleClick () {
count.value += 1;
}
const countAddFive = computed({
get() {
return count.value + 5;
},
set(param) {
count.value = param - 5;
}
});
setTimeout(() => {
countAddFive.value = 100;
}, 3000)
return { count, handleClick, countAddFive };
},
template: `<div @click="handleClick">{{count}} -- {{countAddFive}}</div>`
});
const vm = app.mount('#root');
watch 侦听器
- 具备一定的惰性 lazy
- 参数可以拿到原始值和当前值
- 可以侦听多个数据的变化,用一个侦听器承载
const app = Vue.createApp({
setup() {
const { ref, watch } = Vue;
const name = ref('');
watch(name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
});
return {name}
},
template: `
<div>name: <input v-model="name"></div>
<div>name is {{name}}</div>
`
});
const vm = app.mount('#root');
- 对象的一个键值对的监听,用函数监听
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: ''});
watch(() => nameObj.name, (currentValue, prevValue) => {
console.log(currentValue, prevValue);
});
const { name } = toRefs(nameObj);
return {name}
},
template: `
<div>name: <input v-model="name"></div>
<div>name is {{name}}</div>
`
});
const vm = app.mount('#root');
- 一个侦听器,侦听多个值
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'jack', englishName: 'tom'});
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
console.log(curName, prevName, '-----', curEng, prevEng);
});
const { name, englishName } = toRefs(nameObj);
return {name, englishName}
},
template: `
<div>name: <input v-model="name"></div>
<div>name is {{name}}</div>
<div>englishName: <input v-model="englishName"></div>
<div>name is {{englishName}}</div>
`
});
const vm = app.mount('#root');
watchEffect
- watchEffect 侦听器,偏向于 effect
- 立即执行,没有惰性 immediate
- 不需要传递你要侦听的内容,会自动感知代码依赖,不需要传递很多参数,只要传递一个回调函数
- 不能获取之前数据的值
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'jack', englishName: 'tom'});
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
console.log(curName, prevName, '-----', curEng, prevEng);
});
watchEffect(() => {
console.log(nameObj.name);
console.log(nameObj.englishName);
});
const { name, englishName } = toRefs(nameObj);
return {name, englishName}
},
template: `
<div>name: <input v-model="name"></div>
<div>name is {{name}}</div>
<div>englishName: <input v-model="englishName"></div>
<div>name is {{englishName}}</div>
`
});
const vm = app.mount('#root');
停止侦听器
const app = Vue.createApp({
setup() {
const { reactive, watch, toRefs } = Vue;
const nameObj = reactive({name: 'jack', englishName: 'tom'});
const stop1 = watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
console.log(curName, prevName, '-----', curEng, prevEng);
setTimeout(() => {
stop1(); // 停止侦听器
}, 5000);
});
const stop = watchEffect(() => {
console.log(nameObj.name);
console.log(nameObj.englishName);
setTimeout(() => {
stop(); // 停止侦听器
}, 5000);
});
const { name, englishName } = toRefs(nameObj);
return {name, englishName}
},
template: `
<div>name: <input v-model="name"></div>
<div>name is {{name}}</div>
<div>englishName: <input v-model="englishName"></div>
<div>name is {{englishName}}</div>
`
});
const vm = app.mount('#root');
使 watch 变成非惰性的
watch([() => nameObj.name, () => nameObj.englishName], ([curName, curEng], [prevName, prevEng]) => {
console.log(curName, prevName, '-----', curEng, prevEng);
}, { immediate: true });
Composition API 生命周期函数
const app = createApp({
setup() {
const { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted, onRenderTracked, onRenderTriggered } = Vue;
onBeforeMount(() => {
console.log('onBeforeMount');
});
onMounted(() => {
console.log('onMounted');
});
onBeforeUpdate(() => {
console.log('onBeforeUpdate');
});
onUpdated(() => {
console.log('onUpdated');
});
onBeforeUnmount(() => {
console.log('onBeforeUnmount');
});
onUnmounted(() => {
console.log('onUnmounted');
});
// 每次渲染后重新收集响应式依赖
onRenderTracked(() => {
console.log('onRenderTracked');
});
// 每次触发页面重新渲染时自动执行
onRenderTriggered(() => {
console.log('onRenderTriggered');
});
}
});
provide, inject 跨组件传值
const app = Vue.createApp({
setup() {
const { ref, provide, readonly } = Vue;
const name = ref('jack');
provide('name', readonly(name));
provide('changeName', newName => {
name.value = newName;
});
},
template: `<child />`
});
app.component('child', {
setup() {
const { inject } = Vue;
const name = inject('name');
const changeName = inject('changeName');
const handleClick = () => {
changeName('tom');
}
return { name, handleClick };
},
template: `<div @click="handleClick">{{name}}</div>`
});
const vm = app.mount('#root');
获取 DOM 节点
const app = Vue.createApp({
setup() {
const { ref, onMounted } = Vue;
const hello = ref(null);
onMounted(() => {
console.log(hello.value);
});
return {
hello
}
},
template: `<div ref="hello">hello world</div>`
});
const vm = app.mount('#root');