vue3 がリリースされて久しいですが、公式もデフォルトバージョンを vue3 に切り替え、完全な 中国語ドキュメント も登場しました。同志はもう使っているでしょうか?しばらく試していますが、まだ非常にスムーズです。開発経験を共有したいと思います。皆さんが早く仕事を終えられることを願っています。
魔法の createVNode 関数が vue3 でエクスポートされていることはわかっています。現在の関数は vdom を作成できます。vdom を過小評価しないでください。これを使用すると、予期せぬ効果を生み出すことができます。たとえば、ポップアップ ウィンドウ コンポーネントを実装したいと考えています。
私たちの通常の考え方は、コンポーネントを作成し、プロジェクト内でそれを参照し、制御することです。 v-model を使用して表示と非表示を切り替えますが、これには問題があるため、再利用します。コストはコピーして貼り付ける必要があります。 npmにカプセル化してjsを呼び出して利用するなど効率化する方法がありません。 [関連する推奨事項: vuejs ビデオ チュートリアル ]
ただし、createVNode と render を使用すると、すべての問題が解決されます
// 我们先写一个弹窗组件 const message = { setup() { const num = ref(1) return { num } }, template: `<div> <div>{{num}}</div> <div>这是一个弹窗</div> </div>` }
// 初始化组件生成vdom const vm = createVNode(message) // 创建容器,也可以用已经存在的 const container = document.createElement('div') //render通过patch 变成dom render(vm, container) // 弹窗挂到任何你想去的地方 document.body.appendChild(container.firstElementChild)
上記の一般的な操作の後、次のことができることがわかりました。それをメソッドとしてカプセル化して、必要な場所に配置します。
ドキュメントには、ほとんどの場合、Vue は HTML の構築にテンプレート構文を使用することを推奨していると記載されています。ただし、ユースケースによっては、JavaScript のプログラミング機能をすべて使用する必要があります。このとき、レンダリング機能
が役に立ちます。
jsx とテンプレート構文の利点の比較
jsx とテンプレート構文はどちらも vue でサポートされる記述カテゴリであり、それぞれに異なる点があります。使用シナリオとメソッドは、現在のコンポーネントの実際の状況に基づいて適切に使用する必要があります。
JSX
JSX
とはJavascript 構文拡張の一種、JSX = Javascript XML、つまり Javascript で XML を記述することです。JSX のこの機能により、Javascript の柔軟性だけでなく、セマンティックで直感的な
も備えています。 html。
テンプレート構文の利点
JSX の利点
1、灵活、灵活、灵活(重要的事情说三遍)
2、一个文件能写好多个组件
3、只要JS功底好,就不用记忆那么多命令,上来就是一通输出
4、JS和JSX混用,方法即声明即用,对于懂行的人来说逻辑清晰
##比較#vue は JSX をサポートしているため、コミュニティでは、次のものを区別するかどうかについて議論があります。高いところも低いところも、どっちも変わらないと思います。どちらが良いと思っても、それを使えばいいのです。
欠点を適材適所に置けば、それが長所になります。 私たちは背負わなければなりません。我々の長老たちが伝えた黄金の手段を推進し、すべてのマスターになりましょう。この 2 つを組み合わせることで、無敵の効果を発揮し、軍隊の混乱の中でボスの支持を得ることができます。 次に、私の表面的な理解についてお話します。
一般に、コンテナ コンポーネントは、標準化またはパッケージ化するためです。現在の表示コンポーネントでは、コンテナ コンポーネントで JSX を使用するのが最善です。例: ボタンが 2 つあるので、ボタンを 1 つ作成する必要があります。バックグラウンド データを使用して、どのボタンを表示するかを選択します。ディスプレイでは、私たちの通常のアプローチは、テンプレート内の v-if を通じてさまざまなコンポーネントを制御することです
ただし、JSX と関数コンポーネントを使用すると、ロジックがより明確になり、コードがより単純になることがわかりました。品質が高く、よりエレガントになりました。
//btn1.vue <template> <div> 这是btn1{{ num }} <slot></slot> </div> </template> <script> import { ref, defineComponent } from 'vue' export default defineComponent({ name: 'btn1', setup() { const num = ref(1) return { num } } }) </script> //btn2.vue <template> <div> 这是btn2{{ num }} <slot></slot> </div> </template> <script> import { ref, defineComponent } from 'vue' export default defineComponent({ name: 'btn2', setup() { const num = ref(2) return { num } } }) </script>
// 容器组件 import btn1 from './btn1.vue' import btn2 from './btn2.vue' export const renderFn = function (props, context) { return props.type == 1 ? <btn1>{context.slots.default()}</btn1> : <btn2>{context.slots.default()}</btn2> }
//业务组件 <template> <renderfn>1111111</renderfn> </template> <script> import { renderFn } from './components' console.log(renderFn) export default { components: { renderFn }, setup() { }, }; </script>
依存関係注入 (提供/注入) を上手に活用する
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。 什么是依赖注入 依赖注入 用大白话来说:就是将实例变量传入到一个对象中去 vue中的依赖注入 在vue中,我们套用依赖注入的概念, 我们先来看看他的基本用法 父组件中声明provide 子组件中注入进来 正因为依赖注入的特性,我们很大程度上代替了 接下来我们来举个例子,现在我么有个页面主题色,他贯穿所有组件,并且可以在某一些组件内更改主题色,那我们常规的解决方案中,就是装个vuex然后通过他的api下发颜色值,这时候如果想改,首先要发起 我们来看有了依赖注入 应该怎么处理 首先我们知道vue是单项数据流,也就是子组件不能修改父组件的内容,于是我们就应该想到使用 我们来看代码 将当前子孙组件嵌入到 众所周知,vue3最大的新特性,当属 我们一步步来分析 什么是Composition API 使用 ( 于是在vue3中为了解决当前痛点,避免在大型项目中出现代码逻辑分散,散落在当前组件的各个角落,从而变得难以维护,Composition API横空出世 所谓 基础使用 通过以上代码我们可以看出,一个setup函数我们干出了在 举个例子:大家都用过复制剪贴板的功能,在通常情况下,利用navigator.clipboard.writeText 方法就能将复制内容写入剪切板。然而,细心的你会发现,其实赋值剪切板他是一个通用功能,比如:你做b端业务的,管理系统中到处充满了复制id、复制文案等功能。 于是 这时我们就复用了复制的逻辑,如下代码中直接引入在模板中使用即可 以上代码参考vue版本的 强烈反对在应用的代码中使用 那他的作用是什么呢? 还是逻辑提取,用来代替Mixin,这是在复杂组件中,为了整个代码的可维护性,抽取通用逻辑这是必须要去做的事情,我们可以看element-plus 中table的复用逻辑,在逻辑提取中由于涉及获取 如下 $attrs在我们开发中到底有什么用呢? 通过他,我们可以做组件的 首先有一个标准化的组件,一般是组件库的组件等等 接下来有一个包装组件,他对当前的标准化组件做修饰,从而使结果变成我们符合我们的预期的组件 我们发现当前包装组件中使用了 vue3的组件通常情况下使用vue提供的 代码如下: 使用时 然而经过大佬的奇技淫巧的开发,我们发现可能使用注册vue插件的方式,也能完成组件注册,并且是优雅的! vue插件注册 插件的格式 插件的使用 其实插件的本质,就是在use的方法中调用插件中的 index.js中抛出一个组件插件 组件注册 上述案例中,就是一个简单的优雅的组件注册方式,大家可以发现包括 它能代替大多数的setup函数所表达的内容,具体使用方法,大家请看请移步文档 但是由于setup函数它能返回渲染函数的特性,在当前语法糖中却无法展示,于是遍寻资料,找到了一个折中的办法 如此一来,我们就能在语法糖中返回渲染函数了 我们知道在vue2中想要模拟v-model,必须要子组件要接受一个 然而在vue3中他升级了 父组件中使用v-model 子组件中使用 有了以上语法糖,我们在封装组件的时候,就可以随心所欲了,比如我自己封装可以控制显示隐藏的组件我们就能使用 目前开发中总结的经验就分享到这里了,错误之处,请大佬指出! 然后对vue源码有兴趣的大佬,可以看下这个文章 写给小白(自己)的vue3源码导读 也可以直接看本渣的源码解析github vue-next-analysis 其中包含了vue源码执行思维导图,源码中的代码注释,整个源码的结构,各个功能的单独拆解等。错误之处请大佬指出!其实就是在父组件中声明依赖,将他们注入到子孙组件实例中去
,可以说是能够很大程度上代替全局状态管理
的存在//parent.vue
<template>
<child></child>
<button>添加</button>
</template>
<script>
import { defineComponent, provide, ref } from "vue";
import Child from "./child.vue";
export default defineComponent({
components: {
Child
},
setup() {
const count = ref(0);
const color = ref('#000')
provide('count', count)
provide('color', color)
function setColor(val) {
color.value = val
}
return {
count,
setColor
}
}
})
</script>
//child.vue
//使用inject 注入
<template>
<div>这是注入的内容{{ count }}</div>
<child1></child1>
</template>
<script>
import { defineComponent, inject } from "vue";
import child1 from './child1.vue'
export default defineComponent({
components: {
child1
},
setup(props, { attrs }) {
const count = inject('count');
console.log(count)
console.log(attrs)
return {
count
}
}
})
</script>
全局状态管理
,相信谁都不想动不动就引入那繁琐的vuex
吧dispatch
到Action ,然后在Action
中触发Mutation
接着在Mutation中再去改state
,如此一来,你是不是发现有点杀鸡用牛刀
了,我就改个颜色而已!$attrs
使用它将方法透传给祖先组件,在组件组件中修改即可。//子孙组件child1.vue
<template>
<div>这是注入的内容的颜色</div>
</template>
<script>
import { defineComponent, inject } from "vue";
export default defineComponent({
setup(props, { emit }) {
const color = inject('color');
function setColor() {
console.log(0)
emit('setColor', 'red')
}
return {
color,
setColor
}
}
})
</script>
child.vue
中去,就能利用简洁的方式来修改颜色了善用Composition API抽离通用逻辑
Composition API
也叫组合api ,用好了他,就是你在行业的竞争力,你也有了不世出
的技能data
、computed
、methods
、watch
) 组件选项来组织逻辑通常都很有效。然而,当我们的组件开始变得更大时,逻辑关注点的列表也会增长。尤其对于那些一开始没有编写这些组件的人来说,这会导致组件难以阅读和理解。Composition API
就是在组件配置对象中声明 setup
函数,我们可以将所有的逻辑封装在setup
函数中,然后在配合vue3中提供的响应式API 钩子函数、计算属性API等,我们就能达到和常规的选项式
同样的效果,但是却拥有更清晰的代码以及逻辑层面的复用
<template>
<div>测试compositionApi</div>
</template>
<script>
import { inject, ref, onMounted, computed, watch } from "vue";
export default {
// setup起手
setup(props, { attrs, emit, slots, expose }) {
// 获取页面元素
const composition = ref(null)
// 依赖注入
const count = inject('foo', '1')
// 响应式结合
const num = ref(0)
//钩子函数
onMounted(() => {
console.log('这是个钩子')
})
// 计算属性
computed(() => num.value + 1)
// 监听值的变化
watch(count, (count, prevCount) => {
console.log('这个值变了')
})
return {
num,
count
}
}
}
</script>
传统选项式
中的所有事情,然而这还不是最绝的,通过这些api的组合可以实现逻辑复用,这样我们就能封装很多通用逻辑,实现复用,早点下班Composition API
的逻辑复用能力就派上了用场import { watch, getCurrentScope, onScopeDispose, unref, ref } from "vue"
export const isString = (val) => typeof val === 'string'
export const noop = () => { }
export function unrefElement(elRef) {
const plain = unref(elRef)// 拿到本来的值
return (plain).$el ?? plain //前面的值为null、undefined,则取后面的值,否则都取前面的值
}
export function tryOnScopeDispose(fn) {
// 如果有活跃的effect
if (getCurrentScope()) {
//在当前活跃的 effect 作用域上注册一个处理回调。该回调会在相关的 effect 作用域结束之后被调用
//能代替onUmounted
onScopeDispose(fn)
return true
}
return false
}
//带有控件的setTimeout包装器。
export function useTimeoutFn(
cb,// 回调
interval,// 时间
options = {},
) {
const {
immediate = true,
} = options
const isPending = ref(false)
let timer
function clear() {
if (timer) {
clearTimeout(timer)
timer = null
}
}
function stop() {
isPending.value = false
clear()
}
function start(...args) {
// 清除上一次定时器
clear()
// 是否在pending 状态
isPending.value = true
// 重新启动定时器
timer = setTimeout(() => {
// 当定时器执行的时候结束pending状态
isPending.value = false
// 初始化定时器的id
timer = null
// 执行回调
cb(...args)
}, unref(interval))
}
if (immediate) {
isPending.value = true
start()
}
tryOnScopeDispose(stop)
return {
isPending,
start,
stop,
}
}
//轻松使用EventListener。安装时使用addEventListener注册,卸载时自动移除EventListener。
export function useEventListener(...args) {
let target
let event
let listener
let options
// 如果第一个参数是否是字符串
if (isString(args[0])) {
//结构内容
[event, listener, options] = args
target = window
}
else {
[target, event, listener, options] = args
}
let cleanup = noop
const stopWatch = watch(
() => unrefElement(target),// 监听dom
(el) => {
cleanup() // 执行默认函数
if (!el)
return
// 绑定事件el如果没有传入就绑定为window
el.addEventListener(event, listener, options)
// 重写函数方便改变的时候卸载
cleanup = () => {
el.removeEventListener(event, listener, options)
cleanup = noop
}
},
//flush: 'post' 模板引用侦听
{ immediate: true, flush: 'post' },
)
// 卸载
const stop = () => {
stopWatch()
cleanup()
}
tryOnScopeDispose(stop)
return stop
}
export function useClipboard(options = {}) {
//获取配置
const {
navigator = window.navigator,
read = false,
source,
copiedDuring = 1500,
} = options
//事件类型
const events = ['copy', 'cut']
// 判断当前浏览器知否支持clipboard
const isSupported = Boolean(navigator && 'clipboard' in navigator)
// 导出的text
const text = ref('')
//导出的copied
const copied = ref(false)
// 使用的的定时器钩子
const timeout = useTimeoutFn(() => copied.value = false, copiedDuring)
function updateText() {
//解析系统剪贴板的文本内容返回一个Promise
navigator.clipboard.readText().then((value) => {
text.value = value
})
}
if (isSupported && read) {
// 绑定事件
for (const event of events)
useEventListener(event, updateText)
}
// 复制剪切板方法
//navigator.clipboard.writeText 方法是异步的返回一个promise
async function copy(value = unref(source)) {
if (isSupported && value != null) {
await navigator.clipboard.writeText(value)
// 响应式的值,方便外部能动态获取
text.value = value
copied.value = true
timeout.start()// copied.value = false
}
}
return {
isSupported,
text,
copied,
copy,
}
}
<template>
<div>
<p>
<code>{{ text || '空' }}</code>
</p>
<input>
<button>
<span>复制</span>
<span>复制中!</span>
</button>
</div>
<p>您的浏览器不支持剪贴板API</p>
</template>
<script>
import { ref, getCurrentScope } from 'vue'
import { useClipboard } from './copy.js'
const input = ref('')
const { text, isSupported, copied, copy } = useClipboard()
console.log(text)// 复制内容
console.log(isSupported)// 是否支持复制剪切板api
console.log(copied)//是否复制完成延迟
console.log(copy) // 复制方法
</script>
Composition API
库所有完整版请参考善于使用getCurrentInstance 获取组件实例
getCurrentInstance
支持访问内部组件实例, 通常情况下他被放在 setup中获取组件实例,但是getCurrentInstance
只暴露给高阶使用场景,典型的比如在库中。getCurrentInstance
。请不要把它当作在组合式 API 中获取 this
的替代方案来使用。props、proxy、emit
以及能通过当前组件获取父子组件的关系等,此时getCurrentInstance
的作用无可代替element-plus
代码中利用getCurrentInstance 获取父组件parent
中的数据,分别保存到不同的变量中,我们只需要调用当前useMapState即可拿到数据// 保存数据的逻辑封装
function useMapState<t>() {
const instance = getCurrentInstance()
const table = instance.parent as Table<t>
const store = table.store
const leftFixedLeafCount = computed(() => {
return store.states.fixedLeafColumnsLength.value
})
const rightFixedLeafCount = computed(() => {
return store.states.rightFixedColumns.value.length
})
const columnsCount = computed(() => {
return store.states.columns.value.length
})
const leftFixedCount = computed(() => {
return store.states.fixedColumns.value.length
})
const rightFixedCount = computed(() => {
return store.states.rightFixedColumns.value.length
})
return {
leftFixedLeafCount,
rightFixedLeafCount,
columnsCount,
leftFixedCount,
rightFixedCount,
columns: store.states.columns,
}
}</t></t>
善用$attrs
$attrs
现在包含了所有传递给组件的 attribute,包括 class
和 style
。事件以及props
透传//child.vue
<template>
<div>这是一个标准化组件</div>
<input>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: ['num'],
emits: ['edit'],
setup(props, { emit }) {
function setInput(val) {
emit('edit', val.target.value)
}
return {
setInput
}
}
})
</script>
//parent.vue
<template>
<div>这一层要做一个单独的包装</div>
<child></child>
</template>
<script>
import { defineComponent } from "vue";
import child from './child.vue'
export default defineComponent({
components: {
child
},
setup(props, { emit }) {
function edit(val) {
// 对返回的值做一个包装
emit('edit', `${val}time`)
}
return {
edit
}
}
})
</script>
$attrs
,通过他透传给标准化组件,这样一来,我们就能对比如element UI中的组件做增强以及包装处理,并且不用改动原组件的逻辑。优雅注册全局组件技巧
component
方法来完成全局组件的注册const app = Vue.createApp({})
app.component('component-a', {
/* ... */
})
app.component('component-b', {
/* ... */
})
app.component('component-c', {
/* ... */
})
app.mount('#app')
<div>
<component-a></component-a>
<component-b></component-b>
<component-c></component-c>
</div>
//plugins/index.js
export default {
install: (app, options) => {
// 这是插件的内容
}
}
import { createApp } from 'vue'
import Plugin from './plugins/index.js'
const app = createApp(Root)
app.use(Plugin)
app.mount('#app')
install方法
,那么这样一来,我们就能在install
方法中注册组件。// index.js
import component from './Cmponent.vue'
const component = {
install:function(Vue){
Vue.component('component-name',component)
} //'component-name'这就是后面可以使用的组件的名字,install是默认的一个方法 component-name 是自定义的,我们可以按照具体的需求自己定义名字
}
// 导出该组件
export default component
// 引入组件
import install from './index.js';
// 全局挂载utils
Vue.use(install);
element-plus、vant
等组件都是用如此方式注册组件。善用setup>
<script setup></script>
是在单文件组件 (SFC) 中使 的编译时语法糖。相比于普通的 <script></script>
语法,它具有更多优势:
<script>
import { ref,h } from 'vue'
const msg = ref('Hello World!')
const dynode = () => h('div',msg.value);
</script>
<template>
<dynode></dynode>
<input>
</template>
v-model的最新用法
value props
吐出来一个 叫input的emit
<template>
<child></child>
</template>
<script>
import { defineComponent, ref } from "vue";
import child from './child.vue'
export default defineComponent({
components: {
child
},
setup(props, { emit }) {
const pageTitle = ref('这是v-model')
return {
pageTitle
}
}
})
</script>
title的props
以及规定吐出update:title的emit
<template>
<div>{{ title }}</div>
<input>
</template>
<script>
import { defineComponent } from "vue";
export default defineComponent({
props: ['title'],
emits: ['update:title'],
setup(props, { emit }) {
function setInput(val) {
emit('update:title', val.target.value)
}
return {
setInput
}
}
})
</script>
v-model:visible
单独控制组件的显示隐藏。使用正常的v-model
控制组件内部的其他逻辑,从而拥有使用更简洁的逻辑,表达相同的功能
最后