https://www.zhihu.com/search?type=content&q=withDirectives 3大纲
基础
Proxy/Reflect基础
//生成节点
const dom = new Proxy({}, {
get(target, property) {
return function(attrs = {}, ...children) {
const el = document.createElement(property);
for (let prop of Object.keys(attrs)) {
el.setAttribute(prop, attrs[prop]);
}
for (let child of children) {
if (typeof child === 'string') {
child = document.createTextNode(child);
}
el.appendChild(child);
}
return el;
}
}
});
const el = dom.div({},
'Hello, my name is ',
dom.a({href: '//example.com'}, 'Mark'),
'. I like:',
dom.ul({},
dom.li({}, 'The web'),
dom.li({}, 'Food'),
dom.li({}, '…actually that\'s it')
)
);
//实现简单的链式调用
var pipe = function (value) {
var funcStack = [];
var oproxy = new Proxy({} , {
get : function (pipeObject, fnName) {
if (fnName === 'get') {
//如果想用方法型trigger,return ()=>fn() 即可
return funcStack.reduce(function (val, fn) {
return fn(val);
},value);
}
funcStack.push(window[fnName]);
return oproxy;
}
});
return oproxy;
}
var double = n => n * 2;
var pow = n => n * n;
var reverseInt = n => n.toString().split("").reverse().join("") | 0;
pipe(3).double.pow.reverseInt.get; // 63
关于receiver,d对象本身没有a属性,所以读取d.a的时候,会去d的原型proxy对象找。这时,receiver就指向d,代表原始的读操作所在的那个对象。
const proxy = new Proxy({}, {
get: function(target, key, receiver) {
return receiver;
}
});
const d = Object.create(proxy);
d.a === d // true
关于拦截apply,作为非构造方法调用时
var twice = {
apply (target, ctx, args) {
return Reflect.apply(...arguments) * 2;
}
};
function sum (left, right) {
return left + right;
};
var proxy = new Proxy(sum, twice);
proxy(1, 2) // 6
proxy.call(null, 5, 6) // 22
proxy.apply(null, [7, 8]) // 30
概念
setup(props, context)
代替了create两个钩子,return的值作为template内可用的data值,并且自动解构浅层ref。
//根据element-plus,一般用多个useXX方法,传入对应类型的prop等,生成一类方法/ref/computed
setup(props, { emit }) {
const {
tooltip,
showTooltip,
tooltipVisible,
wrapperStyle,
formatValue,
handleMouseEnter,
handleMouseLeave,
onButtonDown,
onLeftKeyDown,
onRightKeyDown,
setPosition,
} = useSliderButton(props, initData, emit)
}
export const useSliderButton = (props: ISliderButtonProps, initData: ISliderButtonInitData, emit) => {
const {
tooltip,
tooltipVisible,
formatValue,
displayTooltip,
hideTooltip,
} = useTooltip(props, formatTooltip, showTooltip)
}
<div>
<span>{{ count }}</span>
<!-- 直接使用count不需要.value了 -->
<button @click="count++">Increment count</button>
<button @click="nested.count.value ++">Nested Increment count</button>
</div>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return {
count,
nested: {
count
}
}
}
}
官方:
https://v3.cn.vuejs.org/guide/reactivity-fundamentals.html#%E5%A3%B0%E6%98%8E%E5%93%8D%E5%BA%94%E5%BC%8F%E7%8A%B6%E6%80%81
Vue3 Composition API: 对比ref和reactive:
https://zhuanlan.zhihu.com/p/267967246
ref
ref用于创建基础数据类型的响应式变量(采用复制的方式,修改响应式数据不会影响原始数据,数据发生改变,界面就会自动更新),可以理解成 reactive({value: 0 })
特点
- ts指定复杂类型,需要声明泛型
const foo = ref<string | number>('foo')
- 修改值必须要用ref.value
可以用来为源响应式对象上的某个 property 新创建一个ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
toRefs/toRef
可以用来为源响应式对象上的某个 property 新创建一个ref。然后,ref 可以被传递,它会保持对其源 property 的响应式连接。
——use 接收两个参数target和attr,target是对象,attr是对象的属性,返回响应式变量(采用引用的方式,修改响应式数据,会影响原始数据,并且数据发生改变,界面也不会更新)
注: 为了解决reactive析构丢失响应性,Vue3提供了API:toRefs,它可以将一个响应型对象(reactive object) 转化为普通对象(plain object),同时又把该对象中的每一个属性转化成对应的响应式属性(ref)。说白了就是放弃该对象(Object)本身的响应式特性(reactivity),转而给对象里的属性赋予响应式特性(reactivity)
//因为props是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性。不能在props里直接解构
//而toRefs相当于对每一个解构出来的值toRef,
setup(props) {
const { title } = toRefs(props)
console.log(title.value)
}
const state = reactive({
foo: 1,
bar: 2
})
const fooRef = toRef(state, 'foo')
fooRef.value++
console.log(state.foo) // 2
state.foo++
console.log(fooRef.value) // 3
函数式provide和inject
<template>
<MyMarker />
</template>
<script>
import { provide, reactive, readonly, ref } from 'vue'
import MyMarker from './MyMarker.vue
export default {
components: {
MyMarker
},
setup() {
const location = ref('North Pole')
const geolocation = reactive({
longitude: 90,
latitude: 135
})
const updateLocation = () => {
location.value = 'South Pole'
}
//防止修改
provide('location', readonly(location))
provide('geolocation', readonly(geolocation))
//保持单一职责,被注入这inject该方法才能修改provide的数据
provide('updateLocation', updateLocation)
}
}
</script>
watchEffect
watch() 和 watchEffect() 副作用是在 DOM 被挂载或更新之前运行的,所以当侦听器运行副作用时,模板引用还没有被更新。
因此,使用模板引用的侦听器应该用 flush: ‘post’ 选项来定义,这将在 DOM 更新后运行副作用,确保模板引用与 DOM 保持同步,并引用正确的元素。这将在 DOM 被更新后运行副作用,并确保模板引用与 DOM 保持同步并引用正确的元素。
注
- update也是一个被侦听的副作用,但是默认的watchEffect会在update之前执行。
watch//观测
<template>
<div ref="root">This is a root element</div>
</template>
<script>
import { ref, watchEffect } from 'vue'
export default {
setup() {
const root = ref(null)
watchEffect(() => {
console.log(root.value) // => <div></div>
},
{
flush: 'post'
})
return {
root
}
}
}
</script>
侦听一个响应式对象或数组将始终返回该对象的当前值和上一个状态值的引用。为了完全侦听深度嵌套的对象和数组,可能需要对值进行深拷贝。这可以通过诸如 lodash.cloneDeep 这样的实用工具来实现。
Teleport
比如说一个有遮罩功能的按钮,如果遮罩存在于modal-button内,此时因为父子关系,遮罩会自然的和父组件保持一个relative的关系。此时用逻辑组件 teleport to=”body” 即可将遮罩挂载到body(或其它节点
<body>
<div style="position: relative;">
<h3>Tooltips with Vue 3 Teleport</h3>
<div>
<modal-button></modal-button>
</div>
</div>
</body>
const app = Vue.createApp({});
app.component('modal-button', {
template: `
<button @click="modalOpen = true">
Open full screen modal!
</button>
<div v-if="modalOpen" class="modal">
<div>
I'm a modal!
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
`,
//替代
<teleport to="body">
<div v-if="modalOpen" class="modal">
<div>
I'm a teleported modal!
(My parent is "body")
<button @click="modalOpen = false">
Close
</button>
</div>
</div>
</teleport>
data() {
return {
modalOpen: false
}
}
})