Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
打開 Vue3 的官方文檔,它首先會告訴你,Vue 的組件可以按兩種不同的風格書寫:選項式 API 和組合式 API。文檔為我們提供一系列兩種風格的代碼參考,供我們按照偏好進行選擇。
實際上,Vue3 組件可不止兩種寫法,而是多達十幾種!然而,不管是什么寫法,它們都是基于同一個底層系統實現的,概念之間也是彼此相通的,只是使用的接口不同。在實際開發中,我們也不會同時使用到那么多種寫法,但是這并不意味著我們不需要去了解這些寫法!
如果你仔細閱讀 Vue3 的文檔,會發現一些示例或者 api 看起來模棱兩可,不知道這些 api 到底有什么用,或者閱讀 Vue 的源碼時,總是能發現一些對于我們來說意圖不明的邏輯,那么,你可能先要了解一些 Vue 的寫法。先了解一個東西怎么用,再去分析它是怎么實現的。
看完本文章,你會收獲到:
本文章遵從循序漸進的寫作順序,從易到難,輕松上手!
setup 語法糖應該是最常用的寫法了。在 Vue3 中,我們想封裝一個組件,最習慣的做法還是新建一個 Vue 文件,并將組件代碼寫在文件中。具體是:頁面結構寫在 template 中,頁面邏輯寫在 script 中,頁面樣式寫在 style 中。
總之,我們將與該組件相關的代碼都寫在一起、放在一個文件中單獨維護,在需要該組件的地方引入使用。
這里我們使用了 setup 語法糖,直接在 script 中書寫我們的 setup 內部的邏輯。
<template>
<div>{{ name }}</div>
</template>
<script setup lang="ts">
import { ref } from "vue";
const name = ref("天氣好");
</script>
<style scoped></style>
在 App. vue 中引入并使用:
// App.vue
<template>
<User />
</template>
<script setup lang="ts">
import User from "./User.vue";
</script>
<style scoped></style>
注:后續寫法盡管形式不同,但它們最終的目的都是導出一個組件,所以對于組件使用方來說(這里是 App. vue),怎么使用這個組件的代碼都是不變的,所以將不再重復此代碼。
這種寫法也是比較經典的。和 setup 語法糖寫法類似。我們需要新建一個 vue 文件來存儲我們的組件代碼,然后在需要使用該組件的地方對其進行引入。區別在于,我們需要在 script 中導出一個 Vue 實例。
這里我們導出的其實是一個普通對象,該對象包含 data、methods 等屬性。這個對象的屬性都是可選的,即 option,翻譯回來即“選項”。
<template>
<div>{{ name }}</div>
</template>
<script lang="ts">
export default {
data: () => {
return {
name: "天氣好",
};
},
};
</script>
<style></style>
盡管我們在 script 語言塊中導出的默認對象會被 vue 編譯器當成 vue 實例,但不管怎么看,它依舊只是一個 plain object。在定義組件實例方面,vue 提供了一個名為 defineComponent 輔助接口。
<template>
<div>{{ name }}</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data: () => {
return {
name: "天氣好",
};
},
});
</script>
<style></style>
盡管這個接口也不能改變我們導出的是一個普通對象的事實,但是它可以為我們的實例提供強大的類型推導。我們可以把它看成是一個返回 vue 實例的工廠函數,讓我們的代碼看起來更加規范。
在 Vue3 中,官方引入了新的選項 setup,這是 Vue3 選項式寫法和 Vue2 寫法的主要區別。setup 選項的意義在于它允許我們在選項式的寫法中引用和使用組合式的 api,比如 onMounted、ref、reactive 等。但對于我們來說,它對于我們有益的地方還是基于它封裝起來的 setup 語法糖用起來很方便。
<template>
<div>{{ name }}</div>
</template>
<script lang="ts">
export default {
setup() {
return {
name: "天氣好",
};
},
};
</script>
<style></style>
使用 defineComponent 時,它能夠提示我們 setup 將會接收到什么參數:
<template>
<div>{{ name }}</div>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
setup(prop,context) {
return {
name: "天氣好",
};
},
});
</script>
<style></style>
以上寫法我們都是在 template 上書寫我們的頁面結構,這也是最常見的幾種寫法,下面我們來介紹幾種了解 vue 底層必不可少的寫法,渲染函數。
template 模板語法本質上也可以算是一種語法糖。在 vue 編譯器上,template 中的內容最終會被翻譯為渲染函數,掛載到 vue 實例的 render 屬性上。當需要渲染組件時,vue 就執行一次 render,得到對應的虛擬節點樹,最后再轉變為真實 dom。
Vue 允許我們脫離 template,直接自己書寫渲染函數。位置就在導出實例的 render 選項上:
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
data: () => ({ name: "天氣好" }),
render() {
return this.name;
},
});
</script>
<style></style>
在 template 中,我們使用類似 html 的模板語法來描述我們的視圖,在 render 函數中又如何描述呢?vue 提供了兩個 api:createVnode 和 h。二者沒有區別,h 函數只是 createVnode 的縮寫。有了 render 函數,我們就不需要寫 template 了。
<script lang="ts">
import { defineComponent, h } from "vue";
export default defineComponent({
data: () => ({ name: "天氣好" }),
render() {
return h("div", this.name);
},
});
</script>
<style></style>
在上面的示例中,我們使用 h 函數生成了一個 vNode,并 return 出去,作為本組件最終在被使用時渲染出來的效果。
在 template 中我們可以使用 v-if、v-for、slot 等模板語法,在 h 函數中這些概念也是支持的,只是形式不同,這方面官方文檔有具體的示例。總之,template 模板和 render 選項是可以相互替代的。
一般來說,在選項式語法中,setup 方法返回一個對象,該對象暴露給 template,供 template 使用,具體參考第三個例子(vue3 選項式寫法)。如果我們不使用 template,也就沒有返回對象的必要了。
在 Vue3 中,還有另外一種不使用 template 的寫法,就是在 setup 方法中返回一個 render 方法。
<script lang="ts">
import { defineComponent, h, ref } from "vue";
export default defineComponent({
setup() {
const name = ref("天氣好");
return () => h("div", name.value);
},
});
</script>
<style></style>
注意:
就注意中的第一點,我們可以采用下面這種寫法:直接在 defineComponent 中書寫 setup 函數(如果再省一點就是 setup 語法糖的寫法了)。
<script lang="ts">
import { defineComponent, h, ref } from "vue";
export default defineComponent(() => {
const name = ref("天氣好");
return () => h("div", name.value);
});
</script>
<style></style>
以上就是渲染函數的寫法,是不是有點感覺了呢,一下子就學會了兩個 api!后面會提到的 Jsx 寫法其實也應該歸為渲染函數寫法的一種(只要不是 template,而是用 JavaScript 表達頁面結構的,都是渲染函數),但是相對于 h 函數,jsx 并不是純粹的 js,所以我將它們分成了兩類。
有了前面兩類寫法介紹的鋪墊,接下來引入 jsx 語法就沒有什么難理解的點了。
jsx 在 vue 文件中是這樣寫的。在 render 渲染函數返回值處書寫 jsx 替代 h 函數。書寫純 JavaScript 的 h 函數描述結構還是比較繁冗的,jsx 就是簡化了的h 函數寫法。
<script lang="tsx">
import { defineComponent } from "vue";
export default defineComponent({
data() {
return { name: "天氣好" };
},
render() {
return (
<>
<div>{this.name}</div>
</>
);
},
});
</script>
<style></style>
jsx 和 setup 配合食用更加。在選項式風格中使用 setup,在 setup 中使用組合式 api,并且返回 jsx 書寫的渲染函數。
<script lang="tsx">
import { defineComponent, ref } from "vue";
export default defineComponent({
setup() {
const name = ref("天氣好");
return () => <>{name.value}</>;
},
});
</script>
<style></style>
這個其實就是前面介紹過的 defineComponent 傳入 setup 函數寫法:這里的區別只是使用 jsx 替代了 h 函數。
<script lang="tsx">
import { defineComponent, ref } from "vue";
export default defineComponent(() => {
const name = ref("天氣好");
return () => (
<>
<div>{name.value}</div>
</>
);
});
</script>
<style></style>
我們也可以自己將 render 函數執行一遍,然后將得到的 jsx Element 導出,和上一個示例defineComponent 簡寫是十分相似。但是這段代碼的缺點非常致命,它不支持接收外部傳遞來的屬性參數。
<script lang="tsx">
import { ref } from "vue";
export default (() => {
const name = ref("天氣好");
return () => (
<>
<div>{name.value}</div>
</>
);
})();
</script>
<style></style>
不要使用這種寫法。這里會提到這樣寫,只是因為和后面的函數式組件(其二) 寫法有關聯。本寫法與其它寫法都不同,其它寫法導出的都是 JavaScript 對象或者 jsx 對象,而這里我們則是自己執行了一遍渲染函數并得到了虛擬節點,直接將虛擬節點導出去。既然都已經把虛擬節點創建出來了,那自然無法接收 props。
如果 defineComponent 的第一個參數是 setup 函數,那么它的第二個參數則可以為組件的定義添加需要的選項,但一般除了補充 props 選項,不會再需要其它選項了(組合式 api 和 setup 的入參可以完全替代其它選項)。
<script lang="tsx">
import { defineComponent } from "vue";
export default defineComponent(
(props) => {
return () => (
<>
<div>{props.userName}</div>
</>
);
},
{
props: { userName: String },
}
);
</script>
<style></style>
這里 jsx 不再只作為返回值,而是直接被某處使用。它可以是被直接導出,或者用在 template 上。
直接將 jsx 對象導出使用。比前面的寫法更簡潔,做法就是把 setup 里面的內容提到外面。這里需要注意的是我們導出的是一段直接的 jsx 對象(jsx Element),而不是渲染函數。
<script lang="tsx">
import { ref } from "vue";
const name = ref("天氣好");
const User = <>{name.value}</>;
export default User;
</script>
<style scoped></style>
這種寫法可以幫助你在自身的組件內復用一些顆粒度更小的組件,它和 setup 語法糖的寫法非常接近,只是 User 變量可以作為標簽直接使用。
<template>
<User />
</template>
<script setup lang="tsx">
import { ref } from "vue";
const name = ref("天氣好");
const User = <>{name.value}</>;
</script>
<style></style>
你還可以將 User 寫成函數式組件,在本頁面內使用。但它不會將連字符屬性轉換為小駝峰寫法。這和直接用在 template 上的內容都是一樣的,它們都是為了方便在組件本身復用一些常用的組件。
<template>
<User :user-name="name" />
</template>
<script setup lang="tsx">
import { ref } from "vue";
const name = ref("天氣好");
const User = (props: { "user-name": string }) => {
return <>{props["user-name"]}</>;
};
</script>
<style></style>
如果你經常使用 tailwind,你可能就會知道什么情況下會出現小顆粒度的可復用標簽,比如,一個加了一大堆類名的 div 標簽。
以上介紹的所有寫法,都是在 .vue 文件中書寫的,而且也離不開 <script lang="tsx">。接下來的寫法可以讓我們脫離 Vue 的語法塊框架,書寫更像 jsx 的 jsx。
我們需要新建一個 jsx/tsx 文件,然后只要保證導出的仍然是一個組件就可以了。有了前面的鋪墊,我們不難發現,這不就是去掉 script 標簽的選項式寫法嗎?確實!這是因為我故意在前面安排了選項式寫法的例子,所以過渡到這里完全沒有壓力!
// User.tsx
import { ref } from 'vue'
export default {
setup() {
const name = ref('天氣好')
return () => <><div>{name.value}</div></>
}
}
我還是推薦套上 defineComponent:
// User.tsx
import { ref, defineComponent } from 'vue'
export default defineComponent({
setup() {
const name = ref('天氣好')
return () => (<><div>{name.value}</div></>)
}
});
同樣地,前面對于 defineComponent 不同方式的使用這里也都可以的。比如導出普通對象并在 render 或者 setup 中使用 jsx 等等。從 vue 到 jsx,區別只是省下了 script 語法塊。
vue2 選項式寫法+jsx。
// User.tsx
import { defineComponent } from 'vue'
export default defineComponent({
data: () => ({ name: '天氣好' }),
render() {
return <><div>{this.name}</div></>
}
});
導出普通對象:
// User.tsx
export default {
data: () => ({ name: '天氣好' }),
render() {
return <><div>{this.name}</div></>
}
});
Vue 中支持的最像函數式組件的寫法。
// User.tsx
import { ref } from 'vue'
export default function User(props) {
const name = ref('天氣好')
return <><div>{name.value}</div></>
}
該例和前面的自行導出 vNode 對象非常接近,這也是為什么即使后者存在不能接收參數的缺陷我也會提出來,因為二者都是使用接近函數式組件的寫法來描述組件的,但是在 vue 文件中并沒有辦法直接導出這個函數組件,而是需要自行執行得到vNode。而在 jsx 文件中卻可以將其導出,并且支持接收參數。
如果你需要為其定義 props,也不需要使用 defineComponent 的第二個參數為你提供什么 props 選項,而是直接在函數式組件的 props 、emits 屬性上掛載對應的配置。
import { ref } from 'vue'
User.props = {
userName: String
}
function User(props) {
// const name = ref('天氣好')
return <><div>{props.userName}</div></>
}
export default User;
相信習慣了 React 的 fc 的小伙伴,看到這里一定感覺倍感親切。然而 Vue 的 Jsx 終究只是 Vue 的 Jsx,它并沒有像 React 一樣存在那么多強大的 Hooks 和內置組件,而是僅僅只是 h 函數的便捷寫法。在語法上也和 React Jsx 存在諸多區別。和 React Jsx 相比,Vue Jsx 其實和自家的 template 更接近。不過 Vue Jsx 寫法的靈活性還是要比 template 模板高,但官方更推薦使用 template。template 更容易上手且提供了更好的性能優化,除非你想完完全全掌控組件的每一個細節,才需要jsx。
盡管本文提到了很多種寫法,但大多數寫法在大多數時候都是不會派上用場的,也應該不被派上用場。之所以列舉那么多寫法,主要的目的還是為了循序漸進引入 jsx 文件寫法和函數式組件寫法。
可以看出,Vue 的寫法本質上是選項式的。Vue3 在 Vue2 的基礎上引入了 setup 選項和 setup 語法糖,結合組合式 api 后,開發者可以將組件的大部分邏輯都維護在 setup 中,而不是 vue2 中割裂了邏輯的 data+created+methods 選項。
在此基礎上,setup 語法糖支持自動導出組件功能,為我們日常開發帶來了很大的便利。
但是除了使用 template 來表達我們的結構,我們也可以自己使用 render 選項并借助 h 函數或者 jsx 的力量來手寫渲染函數。這些都是在 vue 文件中完成的。
既然都不需要 template 了,那么 vue 文件里就只剩下一個 script 了(我們先忽略 style)。在 jsx 文件中,就允許我們直接書寫導出對象(仍是選項式的寫法),忽略script。
最后是 Vue 的 jsx 文件獨有的特性。它允許我們導出一個函數作為組件,我們稱之為函數式組件(fc,function component),這是 vue 文件和以前所有寫法所不具備的,外形與 React 相近。
總的來說就一句話,Vue 本身仍然是選項式的,但是它現在還額外支持了在 jsx 文件中書寫 fc。
使用哪一種寫法,主要看個人偏好。每種寫法在特定場景下都有它的好處和壞處。選擇哪一種并不重要。但是我還是提幾點個人建議:
為什么不推薦函數式組件的寫法,因為它需要寫在 jsx 文件里。如果你只是想通過 jsx 為你的組件增強某些功能,直接改造 vue 文件更加方便,并且不需要修改你引入文件的后綴(.vue -> .jsx/.tsx)。最重要的是,如果真的想寫函數式組件,為何不試試 React?
當然以上只是我個人的小小看法。如果覺得有用,不妨動動小手點贊和收藏吧!
看到這里,如果你也想試試在 Vue 中寫 jsx,不妨看看這篇文章,分享了怎么在 Vue 項目中配置 jsx 環境,充分發揮 jsx 的優勢!
作者:天氣好
鏈接:https://juejin.cn/post/7303832834623111178
avascript添加事件、移除事件、阻止冒泡、阻止瀏覽器默認行為等寫法(兼容IE/FF/CHROME)
添加事件
var addEvent = function( obj, type, fn ) {
if (obj.addEventListener)
obj.addEventListener( type, fn, false );
else if (obj.attachEvent) {
obj["e"+type+fn] = fn;
obj.attachEvent( "on"+type, function() {
obj["e"+type+fn].call(obj, window.event);
} );
}
};
使用方法:
window.onload = function(){
var it = document.getElementsByTagName("input")[0];
addEvent(it,'click',f1);
}
移除事件
var removeEvent = function( obj, type, fn ) {
if (obj.removeEventListener)
obj.removeEventListener( type, fn, false );
else if (obj.detachEvent) {
obj.detachEvent( "on" +type, obj["e"+type+fn] );
obj["e"+type+fn] = null;
}
};
阻止事件(包括冒泡和默認行為)
var stopEvent = function(e){
e = e || window.event;
if(e.preventDefault) {
e.preventDefault();
e.stopPropagation();
}else{
e.returnValue = false;
e.cancelBubble = true;
}
},
僅阻止事件冒泡
var stopPropagation = function(e) {
e = e || window.event;
if (!+"\v1″) {
e.cancelBubble = true; //否則,我們得使用IE的方法來取消事件冒泡
} else {
e.stopPropagation(); //因此它支持W3C的stopPropation()方法
}
}
!+"\v1″的理解:
其實就是利用各瀏覽器對轉義字符"\v"的理解
在ie瀏覽器中,"\v"沒有轉義,得到的結果為"v"
而在其他瀏覽器中"\v"表示一個垂直制表符(一定程度上相當于空格)
僅阻止瀏覽器默認行為
var preventDefault = function(e) {
e = e || window.event;
if(e.preventDefault) {
e.preventDefault();
}else{
e.returnValue = false;
}
}
取得事件源對象
var getEventTarget = function(e){
e = e || window.event;
var target = event.srcElement ? event.srcElement : event.target;
return target;
}
附:綁定onpropertychange事件
onpropertychange, 它在一個元素的屬性發生變化的時候觸發, 一般用在表單元素中捕獲其value值改變, 它比onchange事件更實時捕獲它的改變,
不過為微軟私有事件。FF大致和它相似的有oninput事件, 不過它只針對textfield與textarea的value屬性。
safari, firefox, chrome與opera都支持此事件。
不同的標簽,有不同的屬性;也有一些通用屬性(在任何標簽內都能寫)屬性名、屬性值不能亂寫,都是W3C規定好的屬性名、屬性值,都不區分大小寫,但推薦小寫雙引號,也可以寫成單引號,甚至不寫,但推薦寫雙引號標簽中不要出現同名屬性,否則后寫的會失效,例如:<input type="text" type="password">
VSCode 刪除行快捷鍵:Ctrl+Shift+K
*請認真填寫需求信息,我們會在24小時內與您取得聯系。