vue开发印象云笔记(项目)

30 阅读5分钟

前置知识

  1. 函数默认参数

ES6 允许为函数的参数设置默认值,即直接写在参数定义的后面。

function fn( {title='a'} = {title:'b'} ){
    console.log(title)
}

fn()  // b  无参数title默认为b
fn({})  // a  有空对象的参数默认title为a
fn({ name: 'hello' })  // a  无title相当于空对象
fn({ title:'hello' })  // hello  有参数title为传的值
fn(a)    // 报错
  1. 模块化

写法一

export var a = 'a'    // a.js

import {a} from 'a.js'  // b.js

写法二

var a = 'a'
export {a}        // a.js

import {a} from 'a.js'  // b.js

写法三

export default function() {   // a.js
    console.log('6')
}

import getNum from 'a.js'     // b.js
getNum()
  1. 类与继承
  • 构造函数
class Persion {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi() {
        console.log(`Hi ${this.name}, I am ${this.age}`)
    }
}

等价于

function Persion(name, age) {
    this.name = name;
    this.age = age;
}
Persion.prototype.sayHi = function(){
    console.log(`Hi ${this.name}, I am ${this.age}`)
}

var p = new Persion('Lee', 16)
  • 静态方法
class EventCenter{
    static fire(){}
    static on(){}
}

等同于

function EventCenter(){}
EventCenter.fire = function(){}
EventCenter.on = function(){}
  • 继承
class Persion {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }
    sayHi() {
        console.log(`Hi ${this.name}, I am ${this.age}`)
    }
}

class Student extends Persion {
    constructor(name, age, score){
        super(name, age);
        this.score = score;
    }
    sayScore(){
        console.log(`Hi ${this.name}, I am ${this.age}, I get ${ths.score}`)
    }
}
  1. 字符串拼接:
var data = 'vue'
var a = `This is ${data}`
// This is vue

项目需求

UI设计:

创建账号--登录--笔记本列表--笔记详情--回收站

交互分析:

  1. 假设用户未登录,当访问网站时,默认跳转到【登录页面】
  2. 用户此时可输入账号密码进行登录。也可点击注册账号跳转到【注册页面】进行注册
  3. 登录成功后默认跳转到【笔记本列表页面】, 用户可新建笔记本、删除笔记本、修改笔记本标题
  4. 用户点击某条笔记本,会跳转到 【笔记页面】,【笔记】页面展示当前笔记本下的笔记列表以及其中一个笔记的详情。用户可在笔记本列表顶部切换笔记本,切换笔记本后会自动展示该笔记本下的笔记列表,并自动打开列表下的第一条笔记
  5. 当用户点击回收站时,会跳转到【回收站】页面,回收站页面里有所有临时删除的笔记。用户可彻底删除笔记,也可恢复笔记到原笔记本
  6. 当用户点击注销时,回到登录页面

接口约定:前后端接口文档规范

vue-cli项目搭建

cd yourdist
npm install -g @vue/cli-init    // 拉取2.x版本
vue init webpack vue-evernote-client
cd vue-evernote-client
npm install
npm run dev
npm run build

环境

npm install --save axios@0.18.0
npm install less --save-dev

生产和开发环境 baseURL 切换

原理:

将存放baseURL的文件抽离出来,在 /build/mock.config.js 文件下判断当前环境:运行npm run dev则为开发环境,写入mock路径;运行npm run build则为生产环境,写入后端提供的路径。这样就实现不同环境请求不同的URL。

实现:

环境判断:

const fs = require('fs')
const path = require('path')

const mockBaseURL = '//localhost:3000'
const realBaseURL = '//note-server.hunger-valley.com'

exports.config = function ({ isDev = true } = { isDev: true }) {
    let fileTxt = `
        module.exports = {
            baseURL: '${isDev ? mockBaseURL : realBaseURL}'
        }
    `
    fs.writeFileSync(path.join(__dirname, '../src/helpers/configURL.js'), fileTxt)
}

开发环境:

require('./mock.config').config({ isDev: true })

生产环境:

require('./mock.config').config({ isDev: false })

封装请求:

import axios from 'axios'
import baseURLConfig from './configURL'

axios.defaults.headers.post['Content-Type']
axios.defaults.baseURL = baseURLConfig.baseURL
axios.defaults.withCredentials = true  // 允许跨域

export default function request(url, type = 'GET', data = {}) {
    return new Promise((resolve, reject) => {
        let option = {
            url,
            method: type,
            validityState(status) {
                return (status >= 200 && status < 300) || status === 400
            }
        }
        if (type.toLowerCase() === 'get') {
            option.params = data
        } else {
            option.data = data
        }
        axios(option).then(res => {
            if (res.status === 200) {
                resolve(res.data)
            } else {
                console.error(res.data)
                reject(res.data)
            }
        }).catch(err => {
            console.error({ msg: '网络异常' })
            reject({ msg: '网络异常' })
        })
    })
}

运行结果:

运行npm run dev,写入了开发环境的baseURL

uTools_1709452355878.png

router跳转的两种方法

声明式(模板)

<router-link :to="">

编程式(js)

router.push({ path: '/home', replace: true })
// 相当于
router.replace({ path: '/home' })
$router已经全局注册到组件中,因此
this.$router.push({ path: '/login' })
等同于
import Router from '@/router/index'
Router.push({ path: '/login' })

PS:

uTools_1709456520796.png

为什么要加 $ 呢?在Vue.js中,通过以 $ 开头的属性或方法,可以区分用户自定义的属性和Vue框架提供的属性/方法。Vue框架内部的一些特殊属性和方法都以 $ 开头,这有助于避免与用户定义的属性和方法发生命名冲突。

vue2组间通信

  1. 父子组件:父组件传一个props相当于向子组件进行一次通信;子组件调用事件 emit 一个 click 相当于向父组件进行一次通信

  2. 爷孙组件:

    • 使用两次父子组件间他通信
    • 使用依赖注入provide+inject来通信
  3. 任意组件:eventBus = new Vue 或 vuex

eventBus.$on 和 eventBus.$emit收发通知

需求举例:由于请求用户名写在created导致在登录后不自动更新头像内的名字;要实现登陆后自动捕获并显示用户名首个字符

实现原理:Vue允许创建多个根节点,所以全局创建一个新的Vue,将被监听的方法$on()挂载到上面,在对应的地方调用$emit()

// 全局创建根节点
import Vue from 'vue'
export default new Vue()
// 监听获取用户名的方法
Bus.$on('userInfo', user => {
    this.username = user.username
})
// 登陆成功后调用该方法
onLogin(){
    ...
    Bus.$emit('userInfo', { 
        username: this.login.username 
    })
}

生命周期

f4102d73b3234747960a2de8c060e954.png

computed

<template>
  <span :title="user.username">{{ slug }}</span>
</template>

<script>
export default {
  data() {
    return {
      username: 'Valley',
    }
  },
  computed: {
    slug() {
      return this.username.charAt(0)
    }
  }
}
</script>

浏览器直接调试封装好的方法

import Auth from '@/apis/auth'
import NotebookList from '@/apis/notebooks'

window.NotebookList = NotebookList  // 直接在浏览器调试

uTools_1710149504910.png

阻止默认事件传播:点击只触发某个标签的事件而不再事件冒泡

uTools_1710298558080.png

登录功能

登陆有错时显示错误信息:v-bind + css

<template>
    <div class="login">
    <p v-bind:class="{ error: register.isError }">{{ register.notice }}</p>
    </div>
</template>

<script>
export default {
    data: {
        return {
            login: {
                isError: false
            }
        }
    }
}
</script>

<style>
.login {
    error {color: red}
}

登录跳转:在created时期获取用户信息isLogin判断是否登录,未登录则跳转到登录页;登陆后跳转到对应页面

created)() {
    Auth.getInfo()
        .then(res => {
            if(!res.isLogin){
                this.$router.push({path: '/login'})
            }
        }
}

封装

axios封装

import axios from 'axios'

axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded'
axios.defaults.baseURL = 'http://xxx.com'
axios.defaults.withCredentials = true

export default function request(url, type = 'GET', data = {}) {
    return new Promise((resolve, reject) => {
        let option = {
            url,
            method: type,
            validateStatus(status) {
                return (status >= 200 && status < 300 || status === 400)
            }
        }
        if (type.toLowerCase() === 'get') {
            option.params = data
        } else {
            option.data = data
        }
        axios(option).then(res => {
            if (res.status === 200) {
                resolve(res.data)
            } else {
                reject(res.data)
            }
        }).catch(err => {
            reject({ msg: '网络异常' })
        })
    })
}

接口封装

import request from '@/helpers/request'

const URL = {
    REGISTER: '/auth/register',
    LOGIN: '/auth/login',
    LOGOUT: '/auth/logout',
    GET_INFO: '/auth'
}
export default {
    register({ username, password }) {
        return request(URL.REGISTER, 'GET', { username, password })
    },
    login({ username, password }) {
        return request(URL.LOGIN, 'POST', { username, password })
    },
    logout() {
        return request(URL.LOGOUT)
    },
    getInfo() {
        return request(URL.GET_INFO)
    }
}

计算距离现在的时间差

uTools_1719405377234.png

使用vuex

Vuex 是 Vue.js 应用程序的状态管理模式。它集中式地存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。以下是 Vuex 的基本用法介绍:

1. 安装 Vuex

在使用 Vuex 之前,你需要先安装它。你可以使用 npm 或 yarn 来安装:

npm install vuex --save
# or
yarn add vuex

2. 创建 Store

Vuex 的核心是 store(仓库)。一个 store 实例单例管理着所有组件的状态。

// store.js
import Vue from 'vue';
import Vuex from 'vuex';

Vue.use(Vuex);

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment(state) {
      state.count++;
    }
  },
  actions: {
    increment(context) {
      context.commit('increment');
    }
  },
  getters: {
    count: state => state.count
  }
});

3. 在 Vue 实例中注册 Store

// main.js
import Vue from 'vue';
import App from './App.vue';
import store from './store';

new Vue({
  render: h => h(App),
  store
}).$mount('#app');

4. 在组件中使用 Vuex

你可以在组件中通过 this.$store 访问 store 的状态和方法。

// Component.vue
<template>
  <div>
    <p>{{ count }}</p>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
export default {
  computed: {
    count() {
      return this.$store.state.count;
    }
  },
  methods: {
    increment() {
      this.$store.commit('increment');
    }
  }
};
</script>

5. Vuex 核心概念

State

Vuex 使用单一状态树——是一个对象包含全部应用层级状态,仅一个状态树对象就包含了全部状态,在开发中将它保存在一个对象中。

const state = {
  count: 0
};

Mutations

要改变 store 中的状态,唯一途径是提交 mutation。这是一个同步事务。

const mutations = {
  increment(state) {
    state.count++;
  }
};

Actions

Actions 类似于 mutations,不同在于:

  • Action 提交的是 mutation,而不是直接变更状态。
  • Action 可以包含任意异步操作。
const actions = {
  increment(context) {
    context.commit('increment');
  }
};

Getters

类似于组件的计算属性,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算。

const getters = {
  count: state => state.count
};

6. 模块化 Store

当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了应对这种情况,Vuex 允许将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter,甚至是嵌套子模块。

const moduleA = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
};

const moduleB = {
  state: { ... },
  mutations: { ... },
  actions: { ... },
  getters: { ... }
};

const store = new Vuex.Store({
  modules: {
    a: moduleA,
    b: moduleB
  }
});

通过模块化管理,可以使代码结构更加清晰,维护起来也更方便。

其他文章

vue开发简易云笔记总结 - 掘金 (juejin.cn)