Vue硅谷外卖笔记

本文介绍了使用Vue开发硅谷外卖应用的过程,涉及同步与异步、nodemon自动重启、单页面应用、Vue Router、Vuex、插槽、ES6语法、axios等内容。详细讲解了在开发中遇到的问题和解决方案,如点击响应延时、滑动效果、动态加载数据等,旨在帮助读者深入理解Vue应用的开发流程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

阻塞和非阻塞

在这里插入图片描述

总结:同步、异步:只是对于热水壶。普通水壶代表同步;响水壶代表异步。虽然都能干活,但响水壶可以在自己完工之后,提示小杨水开了。

阻塞、非阻塞:仅仅代表小杨,立等的属于阻塞(1,3);干别的事了属于非阻塞(2,4)。

所以在上述同步阻塞、同步非阻塞、异步阻塞、异步非阻塞中,异步非阻塞情况下效率较高。

同步和异步的区分

同步含义是“一起”,异步含义是“一边……一边……”

在编程思想里面理解为:

同步:代码的书写顺序和代码的执行顺序一样

异步:代码的书写顺序和代码的执行顺序不一样

例如:setTimeout() btn.onclick = function(){}

console.log("start")
// 先获取button按钮  document文档--->html文档
var btn = document.getElementById("btn")   // =  赋值运算符  
// 给按钮注册一个点击事件  btn叫事件源  click叫事件名   function(){} 函数  当事件发生时,做什么
// 异步代码
btn.onclick = function(){
    alert("登录成功了....")
}
console.log("end")

同步、异步执行顺序:(任务一和任务三是同步任务,任务二是异步任务)
在这里插入图片描述
总结:一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"异步任务队列"的任务,只有等主线程任务执行完毕,"异步任务队列"开始通知主线程,请求执行任务,该任务才会进入主线程执行。

硅谷外卖

nodemon:自动重启项目工程

nodemon 基本配置与使用

单页面应用

动手实现一个单页面应用
单页面应用 VS 多页面应用
vue单页面应用的原理

开发环境、生产环境

前端开发环境、生产环境、测试环境的基本理解和区别

base64编码

小图片,减少请求的次数
图片Base64编码

stylus

stylus:CSS的预处理框架,即将stylus转换为css使用
stylus-loader:让webpack理解stylus
stylus和stylus-loader使用

eslint

Eslint 超简单入门教程

解决点击响应延时 0.3s 问题

如何解决移动端Click事件300ms延迟的问题?
Vue移动端viewport设置和解决点击响应延时0.3s问题

Vue Router

Vue Router官方文档
使用 router-view 和 router-link 实现嵌套路由
vue之router-view组件的使用

$route和 $router

可以理解为$route用来获取路由信息的, $router是用来操作路由的
$router: 路由器对象, 包含一些操作路由的功能函数, 来实现编程式导航(跳转路由)
$route: 当前路由对象, 一些当前路由信息数据的容器, path/meta/query/params

插槽

插槽官方文档
彻底搞懂slot插槽,图文详解
一次性讲明白vue插槽slot

.gitignore

高阶函数

【js】高阶函数是个什么?

watch+$nextTick

vue watch+$nextTick界面更新渲染数据

this.$nextTick()官方介绍:将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新。它跟全局方法 Vue.nextTick 一样,不同的是回调的 this 自动绑定到调用它的实例上。
VUE 关于理解$nextTick()的问题

MSite.vue:

 watch: {
      categorys (value) { // categorys数组中有数据了, 在异步更新界面之前执行
        // 使用setTimeout可以实现效果, 但不是太好
        /*setTimeout(() => {
          // 创建一个Swiper实例对象, 来实现轮播
          new Swiper('.swiper-container', {
            loop: true, // 可以循环轮播
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
            },
          })
        }, 100)*/

        // 界面更新就立即创建Swiper对象
        this.$nextTick(() => {// 一旦完成界面更新, 立即调用(此条语句要写在数据更新之后)
          // 创建一个Swiper实例对象, 来实现轮播
          new Swiper('.swiper-container', {
            loop: true, // 可以循环轮播
            // 如果需要分页器
            pagination: {
              el: '.swiper-pagination',
            },
          })

          new BScroll('.miste-content-wrapper', {
            click: true
          })
        })

      }
    },

es6对象字面量语法

省略语法:

  • 如果定义的属性名称与属性值变量同名,我们可以省略属性名称以及冒号

  • 可以对属性名称书写表达式,通过[]动态的设置属性名称

    之前可以通过[]来获取属性,现在我们可以通过[]来设置属性名

  • 在对象中定义方法可以省略冒号以及function

js有些常量加个中括号

js的语法,在obj的属性名加[]表示属性名是变量。

let k = 'key';

let obj = {
    [k]:0
}

// => {key:0}

mutations.js:

//中括号为计算属性名es6语法:作用是动态的写出当前函数名,方便后期维护
[RECEIVE_ADDRESS] (state, {address}) {
    state.address = address
  }

类似RECEIVE_ADDRESS = ‘receive_address’ 作用

使用vuex的时候,看到有些项目是在store文件夹下面新建一个mutation-types.js文件,内容像下面这样:

export const RECEIVE_ADDRESS = ‘receive_address’ // 接收地址
export const RECEIVE_USER_INFO = ‘receive_user_info’ // 接收用户信息
export const RESET_USER_INFO = ‘reset_user_info’ // 重置用户信息
//…

这样转一下大小写有什么作用?

答:单一职责,就是把 mutation 专门归类到一个文件中了,后面的小写理论上你可以随便写,只不过在一些调试工具显示的信息中会降低可读性,另一点就是,你 commit 的 mutation 的时候,也可以不使用 VERB_FOO 这种变量,直接写后面的小写的部分,但是这样造成的问题是,当你想要更改某一个 mutation 时,必须依赖于它的所有代码都改一遍,耦合性太高了(方便修改)。

VUEX中的dispatch()和commit()

  • commit: 同步操作
    存储 this.$store.commit(‘mutations方法名’,值)
    取值 this.$store.state.mutations方法名
  • dispatch: 异步操作
    存储 this.$store.dispatch(‘action方法名’,值)
    取值 this.$store.getters.action方法名

commit 和dispatch的区别在于commit是提交mutatious的同步操作,dispatch是分发actions的异步操作

在这里插入图片描述

伪类和伪元素

伪类:用于已有元素处于某种状态时为其添加对应的样式,这个状态是根据用户行为而动态变化的。
例如:当用户悬停在指定元素时,可以通过:hover来描述这个元素的状态,虽然它和一般css相似,可以为已有元素添加样式,但是它只有处于DOM树无法描述的状态下才能为元素添加样式,所以称为伪类。

伪元素:用于创建一些不在DOM树中的元素,并为其添加样式。例如,我们可以通过:before来在一个元素之前添加一些文本,并为这些文本添加样式,虽然用户可以看见这些文本,但是它实际上并不在DOM文档中。

CSS 巧用 `::before::after` 伪类
css伪元素:before和:after用法详解

props

关于Vue中props的详解
Prop文档
props详解

正则

前端计划——JavaScript正则表达式快速入门
JS正则表达式入门,看这篇就够了

transform,transition,animation

css3中的变形(transform)、过渡(transtion)、动画(animation)
史上最简单的CSS动画,transform,transition和animation详解

axios

axios中文文档|axios中文网
【vue学习】axios
axios基本用法

短信验证码服务商

容联云通信

验证码(每次指定的src要不一样)

<img class="get_verification" src="http://localhost:4000/captcha" alt="captcha" @click="getCaptcha" ref="captcha">
getCaptcha () {
        // 每次指定的src要不一样
        this.$refs.captcha.src = 'http://localhost:4000/captcha?time='+Date.now()
      }

保存用户信息:

state:

userInfo: {}, // 用户信息

mutation-types:

export const RECEIVE_USER_INFO = 'receive_user_info' // 接收用户信息

mutation:

 [RECEIVE_USER_INFO] (state, {userInfo}) {
    state.userInfo = userInfo

action:

  // 同步记录用户信息
  recordUser({commit}, userInfo) {
    commit(RECEIVE_USER_INFO, {userInfo})
  }

Login.vue:

  // 将user保存到vuex的state
this.$store.dispatch('recordUser', user)

自动登录:

服务器端:
app.js:

app.use(session({
  secret: '12345',
  cookie: {maxAge: 1000*60*60*24 },  //设置maxAge是80000ms,即80s后session和相应的cookie失效过期
  resave: false,
  saveUninitialized: true,
}));

routes/index.js:

/*
根据session中的userid, 查询对应的user
 */
router.get('/userinfo', function (req, res) {
  // 取出userid
  const userid = req.session.userid
  // 查询
  UserModel.findOne({_id: userid}, _filter, function (err, user) {
    // 如果没有, 返回错误提示
    if (!user) {
      // 清除浏览器保存的userid的cookie
      delete req.session.userid

      res.send({code: 1, msg: '请先登陆'})
    } else {
      // 如果有, 返回user
      res.send({code: 0, data: user})
    }
  })
})

客户端:
api/index.js:

// 	根据会话获取用户信息
export const reqUserInfo = () => ajax(BASE_URL+'/userinfo')

action:

// 异步获取用户信息
  async getUserInfo({commit}) {
    const result = await reqUserInfo()
    if (result.code === 0) {
      const userInfo = result.data
      commit(RECEIVE_USER_INFO, {userInfo})
    }
  }

App.vue:

	mounted () {
      // this.$store.dispatch('getAddress')
      this.getAddress()
      this.getUserInfo()
    },

    methods: {
      ...mapActions(['getAddress', 'getUserInfo'])
    },

退出登录

Profile.vue:

this.$store.dispatch('logout')

action:

 // 异步登出
  async logout({commit}) {
    const result = await reqLogout()
    if (result.code === 0) {
      commit(RESET_USER_INFO)
    }  

mutation-types:

export const RESET_USER_INFO = 'reset_user_info' // 重置用户信息

mutation:

 [RESET_USER_INFO] (state) {
    state.userInfo = {}
  }

使用mock模拟接口

Mockjs: 用来拦截 ajax 请求, 生成随机数据返回
http://mockjs.com/
https://github.com/nuysoft/Mock

下载:

npm install mockjs --save 4) 使用(mock/mockServer.js)

使用(mock/mockServer.js)

import Mock from 'mockjs'
import apiData from './data.json'
Mock.mock('/seller', {code:0, data:apiData.seller})
Mock.mock('/goods', {code:0, data:apiData.goods})
Mock.mock('/ratings', {code:0, data:apiData.ratings})

main.js:

import './mock/mockServer' // 加载mockServer即可

callback && callback()

有回调就执行,没有回调就不执行。
相当于

if(callback){

callback();

}

模板解析,一层表达式a,二层表达式a.b,三层表达式a.b.c。

当是三层表达式时,异步显示先显示初始数据,再显示带数据的数据。

【vue踩坑记录】1、“Error in render: “TypeError: Cannot read property ‘0’ of undefined””渲染错误问题

ShopHeader.vue:判断info.supports存在,才显示数据

 v-if="info.supports"

position: absolute/relative实际使用

css定位,relative和absolute的结合使用

通过案例理解position:relative和position:absolute

css层的定位position、absolute、relative层叠加的五条叠加法则

加入transition 动画效果

ShopHeader.vue:
shop-brief-modal和fade-enter-active…同一级

 <transition name="fade">
 </transition>
&.fade-enter-active,&.fade-leave-active
        transition opacity .5s
&.fade-enter,&.fade-leave-to
        opacity 0

异步显示goods数据

  • 触发异步action调用,把数据拿到vuex里
  • 从vuex的state把数据拿到组件里来
  • 在组件模板里展现数据
import {mapState} from 'vuex'
export default {
	mounted() {
      this.$store.dispatch('getShopGoods')
	},
	computed: {
      ...mapState(['goods']),
	}
}

滑动效果分析

BetterScroll

当前分类
当滑动右侧列表时,更新当前分类
点击某个分类项,右侧列表滑动到对应位置
类名:current标识当前分类
设计一个计算属性:currentIndex

根据哪些数据计算?
scrollY: 0, // 右侧滑动的Y轴坐标 (滑动过程时实时变化)
tops: [], // 所有右侧分类li的top组成的数组 (列表第一次显示后就不再变化)
1、在滑动过程中,实时收集scrollY
2、列表第一次显示后,收集tops
3、实现currentIndex的计算逻辑

 :class="{current: index===currentIndex}" 

引入better-scroll

npm install --save better-scroll
import BScroll from 'better-scroll'

ShopGoods.vue:

  mounted() {
      this.$store.dispatch('getShopGoods', () => {// 数据更新后执行
        this.$nextTick(() => { // 列表数据更新显示后执行
          this._initScroll()
          this._initTops()
        })
      })
    },

action.js:

  // 异步获取商家商品列表
  async getShopGoods({commit}, callback) {
    const result = await reqShopGoods()
    if (result.code === 0) {
      const goods = result.data
      commit(RECEIVE_GOODS, {goods})
      // 数据更新了, 通知一下组件
      callback && callback()
    }
  },

伪数组及其转换为真数组原理

ShopGoods.vue:

// Array.prototype.slice.call(lis):将伪数组转换为真数组
 Array.prototype.slice.call(lis).forEach(li => {
          top += li.clientHeight
          tops.push(top)
 })

开发CartControl 组件

ShopGoods.vue:

 import CartControl from '../../../components/CartControl/CartControl.vue'
 
  components: {
      CartControl
  }

CartControl.vue:

props: {
      food: Object
},
methods: {
      updateFoodCount (isAdd) {
        this.$store.dispatch('updateFoodCount', {isAdd, food: this.food})
      }
    }

要给food新增count属性

mutations.js:

[INCREMENT_FOOD_COUNT](state, {food}) {
    console.log(state)
    console.log(food)
    if(!food.count) { // 第一次增加
      // food.count = 1  // 新增属性(没有数据绑定)
      /*
      对象
      属性名
      属性值
       */
      Vue.set(food, 'count', 1) // 让新增的属性也有数据绑定
      // 将food添加到cartFoods中
      state.cartFoods.push(food)
    } else {
      food.count++
    }
  }

问题: 更新状态数据, 对应的界面不变化
原因: 一般方法给一个已有绑定的对象中添加一个新的属性, 这个属性没有数据绑定
解决:
Vue.set(obj, ‘xxx’, value)才有数据绑定
this.$set(obj, ‘xxx’, value)才有数据绑定

Vue—Vue.set的使用

开发 ShopCart 组件

ShopGoods.vue:

<Food :food="food" ref="food"/>
  // 显示food组件 (在父组件中调用子组件对象的方法)
        this.$refs.food.toggleShow()

Food.vue:

toggleShow () {
        this.isShow = !this.isShow
      }

解决事件冒泡:
CartControl.vue:

@click.stop="updateFoodCount(false)"

管理购物项数据:

  • 设计成计算属性(相关数据发生改变就要完全重新计算)
  • 使用vuex(效率更高)

购物车列表的滑动

ShopCart.vue:

if(this.isShow) {
          this.$nextTick(() => {
            // 实现BScroll的实例是一个单例,不然每点击一次创建一个BScroll对象,单击+商品数量不加1
            if(!this.scroll) {
              this.scroll = new BScroll('.list-content', {
                click: true
              })
            } else {
              this.scroll.refresh() // 让滚动条刷新一下: 重新统计内容的高度
            }

          })
        }

异步显示列表ShopRatings组件

action.js:

// 异步获取商家商品列表
  async getShopGoods({commit}, callback) {
    const result = await reqShopGoods()
    if (result.code === 0) {
      const goods = result.data
      commit(RECEIVE_GOODS, {goods})
      // 数据更新了, 通知一下组件
      callback && callback()
    }
  },

ShopRatings.vue:

 mounted () {
      this.$store.dispatch('getShopRatings', () => {
        this.$nextTick(() => {
          new BScroll(this.$refs.ratings, {
            click: true
          })
        })
      })
    }

过滤数组

filterRatings () {
        // 得到相关的数据
        const {ratings, onlyShowText, selectType} = this

        // 产生一个过滤新数组
        return ratings.filter(rating => {
          const {rateType, text} = rating
          /*
            条件1:
                selectType: 0/1/2
                rateType: 0/1
                selectType===2 || selectType===rateType
            条件2
                onlyShowText: true/false
                text: 有值/没值
                !onlyShowText || text.length>0
           */
          return (selectType===2 || selectType===rateType) && (!onlyShowText || text.length>0)
        })
      }

动态计算ul的宽度

ShopInfo.vue:

 // ul的宽度没有被撑开,ul动态计算ul的宽度
        const ul = this.$refs.picsUl
        const liWidth = 120
        const space = 6
        const count = this.info.pics.length
        ul.style.width = (liWidth + space) * count -space + 'px'

如果用css实现:
css3动态计算元素的高度及宽度

解决当前路由刷新异常的bug(/shop/info页面刷新,info初始值为空对象的问题)(watch+$nextTick)

(从/shop/goods切换过/shop/info可以,但是/shop/info刷新不行:shop.vue异步获取数据)

在当前路由刷新,分两步显示,先初始化,接着新来的数据进来后再更新

watch: {
      info () {// 刷新流程--> 更新数据
        this.$nextTick(() => {
          this._initScroll()
        })
      }
    }

实现没有搜索结果的提示显示

Search.vue:

watch: {
      searchShops (value) {
        if(!value.length) { // 没有数据
          this.noSearchShops = true
        } else {// 有数据
          this.noSearchShops = false
        }
      }
    },

@submit.prevent作用

Search.vue:

 <form class="search_form" @submit.prevent="search">

.prevent 表示提交以后不刷新页面,prevent是preventDefault,阻止标签默认行为,有些标签有默认行为,例如a标签的跳转链接属性href等。

submit点击默认行为是提交表单,这里并不需要它提交,只需要执行search方法,故阻止为好。

缓存路由组件对象(浏览器端内存里把组件对象缓存起来)

<keep-alive>
      <router-view/>
    </keep-alive>

好处: 复用路由组件对象, 复用路由组件获取的后台数据

router-link相关属性:replace

<router-link to="/shop/goods" replace>点餐</router-link>

vue路由之replace(无痕浏览)属性

路由组件懒加载(需要的时候才去后台去请求组件代码)

src/router/index.js:

// import MSite from '../pages/MSite/MSite.vue'
// import Search from '../pages/Search/Search.vue'
// import Order from '../pages/Order/Order.vue'
// import Profile from '../pages/Profile/Profile.vue'

const MSite = () => import('../pages/MSite/MSite.vue')
const Search = () => import('../pages/Search/Search.vue')
const Order = () => import('../pages/Order/Order.vue')
const Profile = () => import('../pages/Profile/Profile.vue')

vue路由懒加载及组件懒加载

使用moment实现日期过滤器

npm install --save moment

gshop-client_final\src\fiters\index.js:

import Vue from 'vue'
import moment from 'moment'

// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
  return moment(value).format(formatStr)
})

main.js:

import './fiters' // 加载过滤器

ShopRatings.vue:

<div class="time">{{rating.rateTime | date-format}}</div>
使用 date-fns 代替 moment:
npm install --save data-fns

gshop-client_final\src\fiters\index.js:

import Vue from 'vue'
// import moment from '[添加链接描述](https://uniapp.dcloud.io/)moment'
import format from 'date-fns/format'
// 自定义过滤器
Vue.filter('date-format', function (value, formatStr='YYYY-MM-DD HH:mm:ss') {
  // return moment(value).format(formatStr)
  return format(value, formatStr)
})
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值