vue3.0入門到精通
附B站講解視頻
vue3.0 安裝
前安裝過vue的2.0版本桅滋,你需要把2.0相關(guān)的刪除
npm uni -g vue-cli
安裝vue/cli腳架
npm i -g @vue/cli
檢查版本號,目前安裝vuecli 4.5.4
vue -V
創(chuàng)建:在命令窗口輸入指令
選擇default vue 3
vue create 項目名稱
vue composition API
vue3.0 側(cè)重于解決代碼組織與邏輯復(fù)用問題
目前日月,我們使用的是“options”API 構(gòu)建組件侠坎。 為了將邏輯添加到Vue組件中桃漾,我們填充(options)屬性量窘,如data榜配、methods岂津、computed等誊爹。 這種方法最大的缺點是贞远,它本身不是一個工作的JavaScript代碼畴博。 您需要確切地知道模板中可以訪問哪些屬性以及this關(guān)鍵字的行為。在底層蓝仲,Vue編譯器需要將此屬性轉(zhuǎn)換為工作代碼俱病。正因為如此,我們無法從自動建議或類型檢查中獲益袱结。
Composition API希望將通過當(dāng)前組件屬性亮隙、可用的機制公開為JavaScript函數(shù)來解決這個問題。 Vue核心團隊將組件Composition API描述為“一套附加的垢夹、基于函數(shù)的api溢吻,允許靈活地組合組件邏輯”。 使用Composition API編寫的代碼更易讀果元,并且場景不復(fù)雜促王,這使得閱讀和學(xué)習(xí)變得更容易。
讓我們看到一個非常簡單的組件示例而晒,它使用新的組件Composition API來理解它是如何工作的硼砰。
<template>
? <div id="app">
?? <img alt="Vue logo" src="./assets/logo.png">
?? <div>{{msg}}年齡為{{age}}</div>
?? <button @click="add"> + </button>
? </div>
</template>
?
<script>
?
?
export default {
? name: 'App',
? data() {
?? return {
? ?? msg:'王大合',
? ?? age:18
?? }
? },
? methods:{
?? add() {
? ?? this.age += 1
?? }
? }
?
}
</script>
setup
vue3.0將組件的邏輯都寫在了函數(shù)內(nèi)部,setup()會取代vue2.x的data()函數(shù),返回一個對象,暴露給模板,而且只在初始化的時候調(diào)用一次,因為值可以被跟蹤,所以我們通過vue3來改變編程習(xí)慣
首先引入ref
使用數(shù)據(jù)需要return
import {ref} from 'vue'
setup() {
?? const msg = ref('王大合')
?? const age = ref(18)
?? function add() {
? ?? age.value +=1
?? }
?? return {msg,age,add}
? }
computed
<template>
? <div id="app">
?? <img alt="Vue logo" src="./assets/logo.png">
?? <div>{{msg}}的年齡為{{age}}</div>
?? <div>{{double}}</div>
?? <button @click="add">+</button>
? </div>
</template>
?
<script>
import {computed, ref} from 'vue'
?
export default {
? name: 'App',
? setup() {
?? const msg = ref('王大合')
?? const age = ref(18)
?? const double = computed(() =>{
? ?? return age.value * 2
?? })
?? function add() {
? ?? age.value += 1
?? }
?? return {msg,age,add,double}
? }
}
</script>
?
?
?
reactive
在 setup 函數(shù)里面, 我們適應(yīng)了 Vue3.0 的第一個新接口 reactive 它主要是處理你的對象讓它經(jīng)過 Proxy 的加工變?yōu)橐粋€響應(yīng)式的對象欣硼,
toRefs
用于將響應(yīng)式對象變成普通對象
<template>
? <div id="app">
?? <img alt="Vue logo" src="./assets/logo.png">
?? <div>{{msg}}的年齡為{{age}}</div>
?? <div>{{double}}</div>
?? <button @click="add">+</button>
? </div>
</template>
?
<script>
import {computed, reactive,toRefs} from 'vue'
?
export default {
? name: 'App',
? setup() {
?? const state = reactive({
? ? ?? msg:'王大合',
? ? ?? age:18,
? ? ?? double : computed(() =>{
? ?? return state.age * 2
?? })
?? })
?
?? function add() {
? ?? state.age += 1
?? }
?? return {...toRefs(state),add}
? }
}
</script>
?
?
?
props 和 context
在 Vue2.0 中我們可以使用 props 屬性值完成父子通信题翰,在這里我們需要定義 props 屬性去定義接受值的類型,然后我們可以利用 setup 的第一個參數(shù)獲取 props 使用诈胜。
export default {
? name: 'App',
? components:{
?? Content
? },
? setup() {
?? const state = reactive({
? ? ?? msg:'王大合',
? ? ?? age:18,
? ? ?? double : computed(() =>{
? ?? return state.age * 2
?? })
?? })
?
?? function add() {
? ?? state.age += 1
?? }
?? return {...toRefs(state),add}
? }
}
我們在 App.vue 里面就可以使用該頭部組件豹障,有了上面的 props 我們可以根據(jù)傳進來的值,讓這個頭部組件呈現(xiàn)不同的狀態(tài)焦匈。
<template>
? <div id="app">
?? <img alt="Vue logo" src="./assets/logo.png">
?? <div>{{msg}}的年齡為{{age}}</div>
?? <div>{{double}}</div>
?? <Content :msg='msg' />
?? <button @click="add">+</button>
? </div>
</template>
這里我新建一個新的組件content,在app.vue中引入
<!--? -->
<template>
? <div>{{data}}</div>
</template>
?
<script>
import {ref} from 'vue'
export default {
name:'content',
props:{
? ? msg:String
},
setup(props) {
? ? const data = ref(props.msg)
? ? return {data}
}
}
?
</script>
setup 函數(shù)的第二個參數(shù)是一個上下文對象血公,這個上下文對象中包含了一些有用的屬性,這些屬性在 Vue2.0 中需要通過 this 才能訪問到缓熟,在 vue3.0 中累魔,訪問他們變成以下形式:
setup(props, ctx) {
? console.log(ctx) // 在 setup() 函數(shù)中無法訪問到 this
? console.log(this) // undefined
}
具體能訪問到以下有用的屬性:
- slot
- attrs
- emit
父組件
<template>
? <div id="app">
?? <img alt="Vue logo" src="./assets/logo.png">
?? <div>{{msg}}的年齡為{{age}}</div>
?? <div>{{double}}</div>
?? <Content :msg='msg' @change='showName' />
?? <button @click="add">+</button>
? </div>
</template>
?
<script>
import {computed, reactive,toRefs} from 'vue'
import Content from './components/content.vue'
export default {
? name: 'App',
? components:{
?? Content
? },
? setup() {
?? const state = reactive({
? ? ?? msg:'王大合',
? ? ?? age:18,
? ? ?? double : computed(() =>{
? ? ? ?? return state.age * 2
? ? ?? })
?? })
?? function showName(params) {
? ? ?? alert(params)
?? }
?
?? function add() {
? ?? state.age += 1
?? }
?? return {...toRefs(state),add,showName}
? }
}
</script>
?
?
?
子組件
<!--? -->
<template>
? <div>{{data}}</div>
? <button @click="changeName">打個招呼!</button>
</template>
?
<script>
import {ref} from 'vue'
export default {
name:'content',
props:{
? ? msg:String
},
setup(props,context) {
? ? const data = ref(props.msg)
?? function changeName() {
? ? ?? context.emit('change','Hello,王大合!')
?? }
? ? return {data,changeName}
}
}
?
</script>
watch
監(jiān)聽ref
不指定數(shù)據(jù)源
const a = ref(18)
?
watch(()=>{
? ? console.log(a.value)
})
指定數(shù)據(jù)源
const a = ref(18)
?
? watch(a,()=> {
?
? console.log(a.value)
?
? })
監(jiān)聽reactive
const state = reactive({
? ? ?? msg:'王大合',
? ? ?? age:18,
? ? ?? double : computed(() =>{
? ? ? ?? return state.age * 2
? ? ?? })
?? })
不指定數(shù)據(jù)源
watch(()=>{
? ? console.log(state.age)
})
指定數(shù)據(jù)源
watch(()=>state.age,()=>{
? ? console.log(state.age)
})
回調(diào)函數(shù)參數(shù)以及watche clean,使用clean時候是處理重復(fù)性的watch監(jiān)聽事件
watch(() => state.age,(newVal,oldVal,clean)=> {
? ? console.log(state.msg + "去年年紀:"+oldVal +"今年年紀:" + newVal)
? ? clean(
? ? ? ()=>{
? ? ? ? console.log('clean')
? ? ? }
? ? )
? })
vue3.X+vite+typescript
放棄webpack,使用vite安裝vue3.0
這個是尤大開發(fā)的新工具摔笤,目的是以后替代webpack,原理是利用瀏覽器現(xiàn)在已經(jīng)支持es6的import了垦写,遇到import會發(fā)送一個http請求去加載文件吕世,vite攔截這些請求,做一些預(yù)編譯梯投,省去了webpack冗長打包的時間命辖,提升開發(fā)體驗
npm install -g create-vite-app
create-vite-app vue3-vite
cd vue3-vite
npm install
npm run dev
# 或者使用yarn
yarn add -g create-vite-app
yarn create vite-app <project-name>
安裝依賴
yarn
使用yarn啟動項目
yarn dev
引入typescript
# 安裝 typescript
?
yarn add typescript -D
初始化tsconfig.json
# 然后在控制臺執(zhí)行下面命令
npx tsc --init
將main.js修改為main.ts,同時將index.html里面的引用也修改為main.ts,
然后在script 里添加 lang="ts"
<template>
? <img alt="Vue logo" src="./assets/logo.png" />
? <HelloWorld msg="Hello Vue 3.0 + Vite" />
</template>
?
<script lang="ts">
import HelloWorld from './components/HelloWorld.vue'
?
export default {
? name: 'App',
? components: {
?? HelloWorld
? }
}
</script>
?
修改完之后,重啟就可以訪問項目了分蓖。雖然這樣配置是可以了尔艇,但是打開main.ts會發(fā)現(xiàn)import App from App.vue會報錯:Cannot find module './App.vue' or its corresponding type declarations.,這是因為現(xiàn)在ts還沒有識別vue文件,需要進行下面的配置:
在項目根目錄添加shim.d.ts文件
# powerShell終端,也可以手動創(chuàng)建
New-Item shim.d.ts
添加以下內(nèi)容
declare module "*.vue" {
? import { Component } from "vue";
? const component: Component;
? export default component;
}
安裝vue-router
yarn add vue-router@4.0
這樣可以選擇最新的vue-router 4.0.0的測試版本,這里更新到beta.13
配置vue-router
在項目src目錄下面新建router目錄么鹤,然后添加index.ts文件终娃,在文件中添加以下內(nèi)容
import {createRouter, createWebHashHistory} from 'vue-router'
?
// 在 Vue-router新版本中,需要使用createRouter來創(chuàng)建路由
export default createRouter({
? // 指定路由的模式,此處使用的是hash模式
? history: createWebHashHistory(),
? // 路由地址
? routes: []
})
安裝vuex
同上
yarn add vuex@4.0
目前只能選擇最新測試版
在項目src目錄下面新建store目錄蒸甜,并添加index.ts文件尝抖,文件中添加以下內(nèi)容
import { createStore } from 'vuex'
?
interface State {
? userName: string
}
?
export default createStore({
? state:{
? userName:'王大合'
? }
});
main.ts中引入vuex和vue-router
import { createApp } from 'vue'
import App from './App.vue'
import './index.css'
import router from './router/index'
import vuex from './store/index'
?
const? app = createApp(App)
?
app.use(router)
app.use(vuex)
app.mount('#app')
?
上線小項目todoList
app.vue
<template>
? <div id="app">
?? <div id="nav">
? ?? <router-link to="/">todoList</router-link> |
? ?? <router-link to="/about">About</router-link>
?? </div>
?? <router-view/>
? </div>
</template>
?
<script lang="ts">
?
export default {
? name: 'App'
}
</script>
?
<style lang="scss">
? #app {
? font-family: Avenir, Helvetica, Arial, sans-serif;
? -webkit-font-smoothing: antialiased;
? -moz-osx-font-smoothing: grayscale;
? text-align: center;
? color: #2c3e50;
}
?
#nav {
? padding: 30px;
?
? a {
?? font-weight: bold;
?? color: #2c3e50;
?
?? &.router-link-exact-active {
? ?? color: #42b983;
?? }
? }
}
</style>
配置路由 router/index.ts
import {createRouter, createWebHashHistory} from 'vue-router'
?
// 在 Vue-router新版本中,需要使用createRouter來創(chuàng)建路由
export default createRouter({
? // 指定路由的模式,此處使用的是hash模式
? history: createWebHashHistory(),
? // 路由地址
? routes: [
?? {
?? path: '/',
?? // 必須添加.vue后綴
?? component: () => import('../views/todo-list.vue')
? ?? },
?? {
? ? ?? path: '/about',
? ? ?? name: 'About',
? ? ?? component: () => import('../views/About.vue')
? ?? }
]
})
store的index.ts新建vuex
import { createStore } from 'vuex'
?
interface State {
? userName: string
? taskList: any[]
}
?
export default createStore({
? state: {
? ?? userName: "王大合",
? ?? taskList: []
? },
? mutations:{
?? createTask (state:any, newTask:string) {
? ? ?? state.taskList.push(newTask)
? ?? },
? ?? deleteTask (state:any, index:number) {
? ? ?? state.taskList.splice(index, 1)
? ?? },
? ?? updateStatus (state:any, payload:any) {
? ? ?? const { index, status } = payload
? ? ?? state.taskList[index].isfinished = status
? ?? }
? }
});
在src目錄新建view文件夾,創(chuàng)建todoList和about
todoList
?
<template>
? <div class="home">
?? <!-- input輸入list內(nèi)容 -->
?? <div>
? ? ? <input
?? @keyup.enter="addTask"
? ?? class="input"
? ?? type="text"
? ?? v-model="inputValue"
? ?? placeholder="請輸入" />
?? </div>
?? <!-- todoList內(nèi)容展示和刪除 -->
? ? <ul class="ul">
? ?? <li class="item" v-for="(item, index) in taskList" :key="index">
? ? ?? <p
? ? ?? @click="updateStatus(index, !item.isfinished)"
? ? ?? class="content"
? ? ?? :class="item.isfinished ? 'active' : ''"
? ? ?? >{{item.lable}}</p>
? ? ?? <div class="item-delete" @click="deleteTask(index)">X</div>
? ?? </li>
? ?? <li v-if="taskList.length === 0" class="item-none">暫無數(shù)據(jù)</li>
?? </ul>
? </div>
</template>
?
?
<script lang="ts">
import { ref, computed } from 'vue';
import { useStore } from "vuex";
export default {
? setup() {
?? const store = useStore()
?? const taskList = computed(() => store.state.taskList);
?? const inputValue = ref('');
?? const addTask = () => {
? ?? store.commit('createTask', {
? ? ?? lable: inputValue.value,
? ? ?? isfinished: false
? ?? })
?
? ?? inputValue.value = ''
?? }
?
?? const updateStatus = (index, status) => {
? ?? store.commit('updateStatus', {
? ? ?? index,
? ? ?? status
? ?? })
?? }
?
?? const deleteTask = (index) => {
? ?? store.commit('deleteTask', index)
?? }
?
?? return {
? ?? inputValue,
? ?? taskList,
? ?? addTask,
? ?? updateStatus,
? ?? deleteTask
?? };
? }
}
</script>
?
<style scoped lang='scss'>
* {
? box-sizing: border-box;
? margin: 0;
? padding: 0;
}
ul,
li {
? list-style: none;
?
? text-align: left;
}
.home {
? max-width: 400px;
? margin: 0 auto;
? .input {
?? width: 100%;
?? height: 40px;
?? border-radius: 5px;
?? outline-style: none;
?? border: 2px solid #999;
?? padding: 5px 10px;
? }
? .ul {
?? margin-top: 10px;
? }
?
? .item {
?? height: 40px;
?? line-height: 40px;
?? padding-bottom: 5px;
?? border-bottom: 1px solid #dcdfe6;
?? color: #333333;
? }
? .item-none {
?? height: 40px;
?? line-height: 40px;
?? padding-bottom: 5px;
?? color: #333333;
?? text-align: center;
? }
? .content {
?? float: left;
?? height: 40px;
?? line-height: 40px;
?? cursor: pointer;
? }
? p.active {
?? text-decoration:line-through;
?? color: #999999;
? }
? .item-delete {
?? float: right;
?? width: 25px;
?? text-align: center;
?? cursor: pointer;
? }
}
</style>
about
<template>
? <div class="about">
?? <h1>{{name}}</h1>
?
? </div>
</template>
?
<script lang='ts'>
import { ref, watch } from 'vue';
?
export default {
? name: 'about',
? setup() {
?? const name = ref('王大合出品');
?
?
?
?
?? return {
? ?? name,
?? };
? }
};
</script>
?