vue3.0從零開始到實戰項目
vue3.0中文官網連接:https://v3.cn.vuejs.org/
一、安裝node.js
1. 官方文檔:https://nodejs.org/en/
安裝版本必須大於12.0,建議直接安裝最新穩定版本
2. 安裝 node.js 的同時,一般會自動安裝 npm 的,所以不用單獨安裝 npm 了(如有需要,也可以命令行安裝:npm install -g)
3. 查看版本命令行:node -v 、 npm -v
二、全局安裝vue腳手架
npm install @vue-cli -g
官方文檔:https://cli.vuejs.org/zh/guide/
如果你安裝的是舊版的vue-cli,需要提前卸載之后再重新安裝 @vue-cli,卸載:
npm uninstall vue-cli -g
三、初始化項目
版本查看:vue -V
版本升級:npm update -g @vue-cli
創建項目:vue create projectname
注意:項目名稱不能大寫有字母
進入項目目錄:cd projecname
運行項目:npm run serve
項目目錄結構:
1. node_modules 依賴包,如果刪除了,可以使用命令:npm install 安裝
2. public 存放整個項目默認的 HTML 模板
3. src 源代碼目錄(入口文件:main.js)
4. .browserslistrc
5. .editorconfig 編輯器的默認配置
6. .eslintrc.js
7. .gitignore
8. babel.config.js 配置 babel
9. package.json
10. package-lock.json
11. README.md
以下是手動選擇依賴初始化:

運行項目:
cd projectname
npm run serve


四、vue 3.0 + vite 篇
Vite 是一個 web 開發構建工具,由於其原生 ES 模塊導入方法,它允許快速提供代碼。
通過在終端中運行以下命令,可以使用 Vite 快速構建 Vue 項目。
$ npm init vite-app <project-name>
$ cd <project-name>
$ npm install
$ npm run dev

安裝項目所需插件,比如:
安裝項目生產依賴:npm vie vue-router@next vuex@next element-plus axios -D
安裝項目開發依賴:npm i sass -S
element-plus文檔:https://element-plus.gitee.io/#/zh-CN
安裝路由 Router
# 查看 vue-router 版本
$ npm vie vue-router versions
# 指定版本下載
$ npm i -D vue-router@4.0.0-beta.11
以下是vue3.0的知識點:
Object.defineProperty => Proxy
重構了虛擬DOM
OptionApi => Composition API
setup 是什么?
setup 實際上是一個組件的入口,它運行在組件被實例化的時候,props 屬性被定義之后,實際上等價於 vue2.x 版本的 beforeCreate 和 Created 這兩個生命周期。
setup 接受兩個參數,第一個是 props ,第二個是 context
setup(props, ctx) {
console.log(props, ctx)
}
let Child = { template: `<div>{{title}}</div>`,
setup(props, context) { console.log(props) } } let App = { template: `<div class="container"><Child title="test props"/></div>`, components: { Child } }
Vue.createApp().mount(App, '#app')
reactive
const { reactive, toRefs } = Vue let App = { template: `<div class="container">count: {{count}}<button @click="handlerCountAdd"> Click ++ </button></div>`, setup() { const state = reactive({ count: 0 }) const handlerCountAdd = () => { state.count++ } return { ...toRefs(state), handlerCountAdd } } } Vue.createApp().mount(App, '#app')
toRefs
vue3 提供的 ref 讓我們有機會創建單個的響應式的對象,在 setup 函數中 return 出去之后,在模板中可直接訪問
const App = { template: `<div class="container">{{value}}</div>`,
setup() { const value = ref(1) return { value } } } Vue.createApp().mount(App, '#app')
const App = { template: `<div class="container">{{state.value}}</div>`,
setup() { const state = reactive({ value: 'reactive' }) return { state } } } Vue.createApp().mount(App, '#app')
const App = { template: `<div class="container">{{value}}</div>`,
setup() { const state = reactive({ value: 'reactive' }) return toRefs(state) } } Vue.createApp().mount(App, '#app')
反轉字符串 demo
let App = { template: `<div class="container">value: <input v-model="value"/><br/>rvalue: {{rvalue}}</div>`,
setup() { const state = reactive({ value: '', rvalue: computed(() => state.value.split('').reverse().join('')) }) return toRefs(state) } } Vue.createApp().mount(App, '#app')
數據響應式
在 Vue3 中實現數據響應式的方案由 Vue2 中的 Object.defineProperty
換成了 Proxy
,關於數據響應式的Api上邊說到了一些,還剩下 effect
和 watch
沒有提及到,effect
是數據響應式中重要的一部分,watch
和 computed
都是基於 effect
的。
let App = { template: `<div class="container"> count: {{count}} <button @click="handlerCountAdd"> Click ++ </button></div>`, setup() { const state = reactive({ count: 0, value: 1 }) const handlerCountAdd = () => { state.count++ } watch( () => state.count, val => { console.log('watch', state.count) console.log('watch', state.value) } ) effect(() => { console.log('effect', state.count) console.log('effect', state.value) }) return { ...toRefs(state), handlerCountAdd } } } Vue.createApp().mount(App, '#app')
effect
在響應式數據變化的時候就會執行,執行次數根據響應式數據的個數來決定
let App = { template: `<div class="container"><button @click="handlerCountAdd"> Click ++ </button></div>`, setup() { const r = ref(1) const s = ref(1) const t = ref(1) const handlerCountAdd = () => { r.value *= 1 s.value *= 2 t.value *= 3 } effect(() => { console.log('effect', [r.value, s.value, t.value]) }) return { handlerCountAdd } } } Vue.createApp().mount(App, '#app')
而 watch
則點擊一次 ,只會觸發執行一次
let App = { template: `<div class="container"><button @click="handlerCountAdd"> Click ++ </button></div>`, setup() { const state = reactive({ count: 0, value: 1 }) const r = ref(1) const s = ref(1) const t = ref(1) const handlerCountAdd = () => { r.value *= 1 s.value *= 2 t.value *= 3 } watch([r, s, t], val => { console.log('watch', val) }) return { handlerCountAdd } } } Vue.createApp().mount(App, '#app')
生命周期
beforeCreate => setup(替代) created => setup(替代) beforeMount => onBeforeMount mounted => onMounted beforeUpdate => onBeforeUpdate updated => onUpdated beforeDestroy => onBeforeUnmount destroyed => onUnmounted errorCaptured => onErrorCaptured
全局配置
Vue2.x
創建實例並且掛載 DOM
上
import Vue from "vue"; import App from './App.vue'
new Vue({ render: (h) => h(App) }).$mount("#app");
Vue3.0 新增 api ===> createApp
創建實例
createApp 會產生一個 app 實例,該實例擁有全局的可配置上下文 import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app')
vue 函數
createApp const app = createApp(App) app.use(store) app.use(router) app.mount('#app')
傳了兩個屬性
v-model:selectKeys = "selectKeys"
import {reactive,toRef } from 'vue export default{ setup(props,ctx){ //默認執行一次 //頁面使用 state.selectKeys const state = reactive({ //attr slots emit selectKeys:0 }) //1.直接使用 return { selectKeys:state.selectKeys } //2.導出,頁面上直接使用,數據響應式還帶解構 return { ...toRefs(state) } onMounted(()=>{ }) } }
監聽路由變化
import {reactive,toRef,watch } from 'vue import {useRoute} from 'vue-router' export default{ setup(props,ctx){ const state = reactive({ //attr slots emit selectKeys:0 }) //1.watch監控路由變化 watch(()=>route.path,(newValue)=>{ state.selectKeys = [newValue] }) //2.computed監控路由變化 const selectKeys = computed(()=>{ return [route.path] }) return { selectKeys } } }
vuex
import {reactive,toRef,watch ,computed} from 'vue' import {useRoute} from 'vue-router' export default{ setup(props,ctx){ const route = userRoute() const store = useStore() const state = reactive({ //attr slots emit
selectKeys:0 }) //1.watch監控路由變化
watch(()=>route.path,(newValue)=>{ state.selectKeys = [newValue] }) //2.computed監控路由變化
const selectKeys = computed(()=>{ return [route.path] }) //ref 把普通值變成包裝后的結構,將屬性變成響應式
// ref(store.getters.allTime)
return { selectKeys, allTime:ref(store.getters.allTime) } } } //store.js
import {createStore} from 'vuex export default { state:{ }, getters:{ allTime:()=>{ return 0 } }, mutations:{ }, actions:{ }, modules:{ } }
組件通信
import {reactive,toRef,watch ,computed} from 'vue' import {useRoute} from 'vue-router' import moment from 'moment' export default{ setup(props,ctx){ const state = reactive({ form:{ date:moment(Date.now()).format('YYYY-MM-DD') } }) //方法函數
const onSubmit =()=>{ //傳給父組件
this.$emit('handlePlan',state.form) } return { ...toRefs(state), onSubmit } } } //父組件
<Child @handlePlan="handlePlan" />
import {reactive,toRef,watch ,computed} from 'vue' import {useRoute} from 'vue-router' import moment from 'moment' export default{ setup(props,ctx){ const state = reactive({ form:{ date:moment(Date.now()).format('YYYY-MM-DD') } }) const handlePlan = (plan)=>{ console.log(plan) } return { handlePlan } } }
封裝 API
// 環境變量
VUE_APP_URL = 'http://www.xxx.com:3000'
import axios from 'axios const instance = axios.create({ baseURL:process.env.VUE_APP_URL, timeout:3000 }) instance.interceptors.request.use((config)=>{ return config }) instance.interceptors.response.use((res)=>{ return res.data.data },err=>{ return Promise.reject(err) }) export function request(opts){ return instance(opts) }
//request.js
import {request } from '../utils/axios' export function getPlanList(){ return request({url:'/plan',method:'get'}) } export function addPlan(data){ return request({url:'/plan',method:'post',data}) } export function deletePlan(){ return request({url:'/plan',method:'delete',params:{id}}) } //action_type.js
export const SET_PLAN_LIST = 'SET_PLAN_LIST' export const ADD_PLAN = 'ADD_PLAN' export const DELETE_PLAN = 'DELETE_PLAN'
//store.js
import {createStore} from 'vuex' export * as types from './action_type' import * as api from './request' export default { state:{ }, getters:{ allTime:()=>{ return 0 } }, mutations:{ [type.ADD_PLAN](state,payload){ state.planList = [...state.planList,payload] }, [type.DELETE_PLAN](state,payload){ state.planList.filter(item=>{ return item._id !=payload._id }) }, [type.SET_PLAN_LIST](state,payload){ }, }, actions:{ //restful api根據不同方法返回不同的資源
async [type.ADD_PLAN]({commit},payload){ let plan = await api.addPlan(payload) commit(type.ADD_PLAN,plan) }, async [type.DELETE_PLAN]({commit},payload){ let plan = await api.deletePlan(payload) commit(type.DELETE_PLAN,plan) }, async [type.SET_PLAN_LIST]({commit},payload){ let plan = await api.getPlanList(payload) commit(type.SET_PLAN_LIST,plan) }, }, modules:{ } }
使用數據
import {reactive,toRef,watch ,onMounted,onUpdated,compile,computed} from 'vue' import {useStore} from 'vuex' import moment from 'moment' import * as types from '@/store/action_types' export default{ setup(props,ctx){ const store = useStore() // const state = reactive({
// planList:store.state.planList //這樣取的是默認值
// })
onMounted(()){ store.dispatch(types.SET_PLAN_LIST) } //時間格式化方法
const formatDate = (value)=>{ return moment(value).format('YYYY-MM-DD') } return { ...toRefs(state.store), formatDate } } }
簡版 vue
//1.創建虛擬節點,將虛擬節點轉化為真實節點 //2.組件的實現 setup //3.reactive api實現effect //4.diff算法 //5.vite
let { render} = Vue const state = { count:0 } const vnode = { tag:'div', props:{color:'red'}, children:[ { tag:'p', props:{color:'blue}, children:[ 'vue@3-計數器' ] }, { tag:'p', props:{ onClick:()=>{ alert(state.count) } } children:[ 'vue@3-計數器' ] } ] } render(vnode,app) export function render(vnode,container){ // 渲染頁面的方法叫patch //1.第一次渲染 2.dom-diff patch(null,vnode,container) } /** * n1 老的虛擬節點 * n2 新的虛擬節點 * container 容器 */ function patch(n1,n2,container){ //組件的虛擬節點是一個對象,tag是一個對象 //如果是組件,tag可能是個對象 //后續diff可以執行這個方法 if(typeof n2.tag ==='string'){ //標簽 mountElement(n2,container) }else if(typeof n2.tag==='object'){ } } function mountElement(vnode,container){ const { tag,children,props } = vnode //虛擬節點和真實節點做映射關系 let el = (vnode.el = nodeOps.createElement(tag)) if(Array.isArray(children)){ mountChild(children,el) }else{ nodeOps.hostSetElementText(el,children) } container.insert(el,container) } function mountChild(children,container){ for(var i=0;i<children.length;i++){ let child = children[i] patch(null,child,container) } } //節點操作方法 exoprt const nodeOps = { //插入元素節點 insert(child,parent,anchor){ if(anchor){ parent.insertBefore(child,anchor) }else{ parent.appendChild(child) } }, //移除節點 remove(child){ const parent = child.parentNode; parent && parent.removeChild(child) }, //創建節點 createElement(tag){ return document.createElement(tag) }, //設置文本內容 hostSetElementText(el,text){ el.textContent = text } }
未完待續。。。