Vue3.0实战项目

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
    }
}

未完待续。。。

原文地址:https://www.cnblogs.com/Happy-Lu/p/13162282.html