顽皮的雪狐七七

顽皮的雪狐七七 查看完整档案

北京编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑

学习探索使我内心平静,
头脑风暴使我激动兴奋,
我努力向前,
在看不到终点的路上,欣赏风景~

个人动态

顽皮的雪狐七七 发布了文章 · 2020-12-31

Vuex(三) —— 纯手写一个超简单的Vuex

目录

  • 分析Vuex的功能
  • 下载模板
  • 分析模块结构
  • 实现install函数
  • 实现Store类
  • 替换vuex

前面学了Vuex的概念和使用,还用购物车做了一个完整的案例,下面看看手写一个简单的Vuex

分析Vuex的功能

  • 首先导入vuex的是一个对象
  • 使用use挂载到Vue的实例上,use方法调用vuexinstall方法
  • 调用new Vuex.Store方法初始化实例
  • 传入参数是一个对象,里面有stategettersmutationsactions等属性
  • 使用的时候直接使用$store.state$store.getters来访问store里面的状态和getter
  • 修改状态可以直接使用$store.commit提交mutation
  • 在执行异步操作可以使用$store.dispatch分发action

下载模板

  • vuex-myvuex-demo-temp
  • 里面使用了vuex进行简单的模拟操作,这里只是简单实现里面的stategettermutationsactions属性,其余的方法并不实现。

分析模块结构

需要一个vuex的模块,这个模块需要导出一个install方法和一个Store

let _Vue = null
class Store {}

// install接收一个参数,Vue构造函数,后面在Store类中还要使用构造函数,所以在全局定义一个_Vue
function install (Vue) {
  _Vue = Vue
}

export default {
  Store,
  install
}

实现install函数

// install接收一个参数,Vue构造函数,后面在Store类中还要使用构造函数,所以在全局定义一个_Vue
function install (Vue) {
  _Vue = Vue
  // 1. 创建Vue实例传入的store对象注入到Vue原型上的$store,在所有组件中用this.$store都可以获取到Vuex的仓库,从而共享状态
  // 2. 这里我们获取不到Vue的实例,所以这里通过混入beforeCreate来获取Vue实例,从而拿到选项中的store对象
  _Vue.mixin({
    beforeCreate () {
      // 这里的this就是Vue的实例
      // 首先判断当前Vue的实例的options中是否有store,当创建根实例的时候,会把store注入到Vue的实例上,如果是组件实例,并没有store选项就不需要做这件事情
      if (this.$options.store) {
        // 给Vue的原型上挂载$store
        _Vue.prototype.$store = this.$options.store
      }
    }
  })
}

实现Store类

class Store {
  // 构造函数接收一个参数是对象
  constructor (options) {
    // 这里对对象进行解构,并且赋默认值为空对象,避免没有传当前属性
    const {
      state = {},
      getters = {},
      mutations = {},
      actions = {}
    } = options
    // 将state属性进行响应式处理
    this.state = _Vue.observable(state)
    // 对getters属性进行处理
    // getters是一个对象,对象中有一些方法,这些方法都需要接收state参数,并且最终都有返回值,这些方法都是获取值,所以可以使用Object.defineProperty将这些方法转换成get访问器
    // 1. 先定义一个this.getters让外部可以直接访问,然后初始化成一个没有原型对象的空对象
    this.getters = Object.create(null)
    // 2. 遍历所有的getters的key,把对应的key注册到this.getters对象中,定义一个get属性,返回key对应的getters中方法的执行结果,并传入state
    Object.keys(getters).forEach(key => {
      Object.defineProperty(this.getters, key, {
        get: () => getters[key](state)
      })
    })
    // 内部属性是私有属性,标识下划线_,不希望外部访问
    // 对mutations属性进行处理
    this._mutations = mutations
    // 对actions属性进行处理
    this._actions = actions
  }

  // 在commit方法中获取_mutations
  // 接收两个参数,第一个参数是type,方法名称,第二个参数是payLoad,调用方法的参数
  commit (type, payload) {
    //通过type找到this._mutations中的方法并调用,传入参数payload
    this._mutations[type](this.state, payload)
  }

  // 在dispatch方法中获取_actions
  // 实现方式和commit一样
  dispatch (type, payload) {
    // 第一个参数是context,这里简单模拟就传入this,这个里面就有我们需要的state,commit等
    // 第二个参数是payload
    this._actions[type](this, payload)
  }
}

替换vuex

index.js中的vuex的导入替换成../myVuex,打开浏览器,可以看到mutationactions可以正常执行

import Vuex from '../myVuex'
查看原文

赞 1 收藏 0 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-31

Vuex(二) —— 用Vuex完成购物车案例

目录

  • 需求
  • 需求分析

    • 组件分析
    • 组件通信
  • 开发

    • 准备环境
    • 准备模块结构
    • 商品列表组件

      • 展示商品列表
      • 添加购物车
    • 我的购物车组件

      • 购物车列表
      • 商品数量和统计功能
      • 删除购物车商品
    • 购物车列表组件

      • 购物车列表
      • 全选操作
      • 数字加减并统计小计
      • 删除功能
      • 统计总数量和总钱数
      • 处理金额小数的问题
    • 本地存储
  • 完整案例

上一节介绍了Vuex的核心原理及简单使用,这里来一个实际案例

需求

  • 商品列表展示商品、价格和【加入购物车】按钮

    • 点击【加入购物车】按钮加入购物车,【我的购物车】提示数量增加
  • 【我的购物车】按钮

    • 鼠标悬停出现popover,展示购物车里面的商品,价格数量,【删除】按钮,还有总数量和总价格,还有【去购物车】按钮
    • 【删除】按钮可以删除整个商品,总价和数量都会改变
    • 点击【去购物车】按钮可以跳到购物车界面
  • 展示多选框,商品,单价,数量及【加减按钮车】,小计,【删除】按钮,总量和总价,【结算】按钮

    • 数量加减改变数量,小计,总数量和总价
    • 【删除】按钮删除整个商品
    • 多选框不选中的不计入总数量和总价格。
  • 刷新页面,状态还在,存在本地存储中

需求分析

组件分析

  • 路由组件

    • 商品列表(①)
    • 购物车列表(②)
  • 我的购物车弹框组件(③)

组件通信

②和③都依赖购物车的数据,①中点击添加购物车,主要把数据传递给②和③,②和③之间的数据修改也互相依赖,如果没有Vuex需要花时间精力在如何在组件中传值上。

开发

准备环境

  1. 下载模板vuex-cart-demo-template,里面已经将路由组件、样式组件和数据都写好了,我们只要负责实现功能即可。项目中还有一个server.js的文件,这个是node用来模拟接口的。
const _products = [
  { id: 1, title: 'iPad Pro', price: 500.01 },
  { id: 2, title: 'H&M T-Shirt White', price: 10.99 },
  { id: 3, title: 'Charli XCX - Sucker CD', price: 19.99 }
]

app.use(express.json())
// 模拟商品数据
app.get('/products', (req, res) => {
  res.status(200).json(_products)
})
// 模拟支付
app.post('/checkout', (req, res) => {
  res.status(200).json({
    success: Math.random() > 0.5
  })
})
  1. 首先npm install安装依赖,之后node server将接口跑起来,然后再添加终端输入npm run serve让项目跑起来,这个时候访问http://127.0.0.1:3000/products可以访问到数据,访问http://localhost:8080/可以访问到页面

准备模块结构

  1. store文件夹中创建modules文件夹,创建两个模块products.jscart.js

  1. products.jscart.js文件中搭建基本结构
const state = {}
const getters = {}
const mutations = {}
const actions = {}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}
  1. index.js中导入并且引用模块
import Vue from 'vue'
import Vuex from 'vuex'
// 1. 导入模块
import products from './modules/products'
import cart from './modules/cart'
Vue.use(Vuex)

export default new Vuex.Store({
  state: {
  },
  mutations: {
  },
  actions: {
  },
  // 2. 引用模块
  modules: {
    products,
    cart
  }
})

商品列表组件

  • 展示商品列表
  • 添加购物车

展示商品列表

  1. products.js中要实现下面的方法
  • state中定义一个属性记录所有的商品数据
  • mutations中添加方法去修改商品数据
  • actions中添加方法异步向接口请求数据
// 导入axios
import axios from 'axios'
const state = {
  // 记录所有商品
  products: []
}
const getters = {}
const mutations = {
  // 给products赋值
  setProducts (state, payLoad) {
    state.products = payLoad
  }

}
const actions = {
  // 异步获取商品,第一个是context上下文,解构出来要commit
  async getProducts ({ commit }) {
    // 请求接口
    const { data } = await axios({
      method: 'GET',
      url: 'http://127.0.0.1:3000/products'
    })
    // 将获取的数据将结果存储到state中
    commit('setProducts', data)
  }
}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}
  1. products.vue中将原来的data删除,导入模块并使用
<script>
// 导入需要的模块
import { mapActions, mapState } from 'vuex'
export default {
  name: 'ProductList',
  // 创建计算属性,映射products数据,因为开启了命名空间,这里添加了命名空间的写法,后面是映射的属性products
  computed: {
    ...mapState('products', ['products'])
  },
  // 把actions里面的方法映射进来,第一个依旧是命名空间的写法
  methods: {
    ...mapActions('products', ['getProducts'])
  },
  // 组件创建之后调用getProducts获取数据
  created () {
    this.getProducts()
  }
}
</script>
  1. 打开浏览器,可以看到商品界面已经出现了三个商品。

添加购物车

把当前点击的商品存储到一个位置,将来在购物车列表组件中可以访问到,所以需要一个位置记录所有的购物车数据,这个数据在多个组件中可以共享,所以将这个数据放在cart模块中

  1. 在模块cart.js中写数据
const state = {
  // 记录购物车商品数据
  cartProducts: []
}
const getters = {}
const mutations = {
  // 第二个是payLoad,传过来的商品对象
  addToCart (state, product) {
    // 1. 没有商品时把该商品添加到数组中,并增加count,isChecked,totalPrice
    // 2. 有该商品时把商品数量加1,选中,计算小计
    // 判断有没有该商品,返回该商品
    const prod = state.cartProducts.find(item => item.id === product.id)

    if (prod) {
      // 该商品数量+1
      prod.count++
      // 选中
      prod.isChecked = true
      // 小计 = 数量 * 单价
      prod.totalPrice = prod.count * prod.price
    } else {
      // 给商品列表添加一个新商品
      state.cartProducts.push({
        // 原来products的内容
        ...product,
        // 数量
        count: 1,
        // 选中
        isChecked: true,
        // 小计为当前单价
        totalPrice: product.price
      })
    }
  }
}
const actions = {}

export default {
  namespaced: true,
  state,
  getters,
  mutations,
  actions
}
  1. products.vue中导入cart的添加购物车mutation
<template>
  <div>
    ...
    <el-table
      :data="products"
      style="width: 100%">
      ...
      <el-table-column
        prop="address"
        label="操作">
        <!-- 这一行可以通过插槽获取作用域数据 -->
        <!-- <template slot-scope="scope"> 这是2.6之前的写法,2.6之后已经过时了换成下里面的写法了-->
        <template v-slot="scope">
          <!--添加点击事件,传入当前列表-->
          <el-button @click="addToCart(scope.row)">加入购物车</el-button>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script>
import { mapActions, mapMutations, mapState } from 'vuex'
export default {
  name: 'ProductList',
  computed: {
    ...mapState('products', ['products'])
  },
  methods: {
    ...mapActions('products', ['getProducts']),
    // 将添加购物商品的数据映射到methods中
    ...mapMutations('cart', ['addToCart'])
  },
  created () {
    this.getProducts()
  }
}
</script>

<style></style>
  1. 点开浏览器,可以点击加入购物车按钮,点开调试台可以看到数据的变化

我的购物车组件

  • 购买商品列表
  • 统计购物车总数和总价
  • 删除按钮

购物车列表

  1. component/pop-cart.vue中导入购物车数据
<template>
  <el-popover
    width="350"
    trigger="hover"
  >
  <!-- 这里是cartProducts的数据,不需要修改 -->
    <el-table :data="cartProducts" size="mini">
      <el-table-column property="title" width="130" label="商品"></el-table-column>
      ...
    </el-table>
    ...
  </el-popover>
</template>

<script>
// 导入vuex模块
import { mapState } from 'vuex'
export default {
  name: 'PopCart',
  computed: {
    // 把cart模块中的cartProducts导入
    ...mapState('cart', ['cartProducts'])
  }
}
</script>

<style></style>
  1. 打开浏览器,点击商品添加购物车,可以看到弹窗里有新加的商品

商品数量和统计功能

  1. 因为总数和总量可以用store中的getters来写,因为是对数据的简单修改,在cart.jsgetters中这么写:
const getters = {
  // 接收state为参数,返回结果
  totalCount (state) {
    // 返回数组中某个元素的和,用reduce方法
    // reduce方法接收两个参数,第一个参数是函数,第二个参数是起始数(这里从0开始)
    // 函数内部接收两个参数,第一个参数是求和变量,第二个数组的元素
    return state.cartProducts.reduce((sum, prod) => sum + prod.count, 0)
  },
  // 与上面同样写法
  totalPrice () {
    return state.cartProducts.reduce((sum, prod) => sum + prod.totalPrice, 0)
  }
}
  1. components/pop-cart.vue中引用
<template>
  <el-popover
    width="350"
    trigger="hover"
  >
    ...
    <div>
      <!-- 总数和总量也改成插值表达式 -->
      <p>共 {{ totalCount }} 件商品 共计¥{{ totalPrice }}</p>
      <el-button size="mini" type="danger" @click="$router.push({ name: 'cart' })">去购物车</el-button>
    </div>
    <!-- 徽章这里,将value修改成totalCount -->
    <el-badge :value="totalCount" class="item" slot="reference">
      <el-button type="primary">我的购物车</el-button>
    </el-badge>
  </el-popover>
</template>

<script>
// 把mapGetters导入
import { mapGetters, mapState } from 'vuex'
export default {
  name: 'PopCart',
  computed: {
    ...mapState('cart', ['cartProducts']),
    // 把cart模块中的totalCount和totalPrice导入
    ...mapGetters('cart', ['totalCount', 'totalPrice'])
  }
}
</script>

<style>

</style>
  1. 打开浏览器,添加两个商品,可以看到徽章和总计都发生了变化

删除购物车商品

删除商品要修改cart模块中的state,所以要在cart模块中添加一个mutation

  1. cardmutation中添加
const mutations = {
  addToCart (state, product) {
    ...
  },
  // 删除购物车商品,第二个参数是商品id
  deleteFromCart (state, prodId) {
    // 使用数组的findIndex获取索引
    const index = state.cartProducts.findIndex(item => item.id === prodId)
    // 判断这个是不是等于-1,如果不是说明有这个商品,就执行后面的删除该元素
    // splice接收删除元素的索引,第二个元素是删除几个元素,这里写1
    index !== -1 && state.cartProducts.splice(index, 1)
  }
}
  1. components/pop-cart.vue中引用
<template>
  <el-popover
    width="350"
    trigger="hover"
  >
    <el-table :data="cartProducts" size="mini">
      ...
      <el-table-column label="操作">
        <!-- 获取当前元素的id,添加slot插槽 -->
        <template v-slot="scope">
          <el-button size="mini" @click="deleteFromCart(scope.row.id)">删除</el-button>
        </template>
      </el-table-column>
    </el-table>
    ...
  </el-popover>
</template>

<script>
// 导入mapMutations模块
import { mapGetters, mapMutations, mapState } from 'vuex'
export default {
  name: 'PopCart',
  computed: {
    ...
  },
  methods: {
    // 把cart模块中的deleteFromCart映射到methods中
    ...mapMutations('cart', ['deleteFromCart'])
  }
}
</script>

<style></style>
  1. 在浏览器中预览,添加商品之后点击删除按钮当前商品删除成功

购物车列表组件

  • 购物车列表
  • 全选操作
  • 数字加减并统计小计
  • 删除功能
  • 统计选中商品价格数量

购物车列表

  1. 在views/cart.vue中引入vuex
<template>
  <div>
    ...
    <!-- 这里也要写成cartProducts -->
    <el-table
      :data="cartProducts"
      style="width: 100%"
    >
      ...
    </el-table>
    ...
  </div>
</template>

<script>
// 导入vuex
import { mapState } from 'vuex'
export default {
  name: 'Cart',
  computed: {
    // 将cartProducts映射到computed中
    ...mapState('cart', ['cartProducts'])
  }
}
</script>

<style></style>
  1. 在浏览器中看,添加商品到我的购物车,购物车列表中有了对应的数据

全选操作

  • 点击子checkbox,选中变不选中,不选中变选中

    • checkbox的状态是其商品的isChecked的值决定
    • 使用mutation
  • 点击父checkbox的时候,子checkbox与父保持一致,并且会重新进行计算值。全部点中子checkbox,父checkbox也会选中

    • checkbox的状态,是购物车页面单独显示的,不需要写到store中, 直接写到当前组件。
    • 其依赖子checkboxisChecked状态,所以使用计算属性
    • 改变父checkbox的状态,store的子状态也需要改变,不需要定义方法,设置其set方法即可
  1. 先写改变子checkbox状态的mutation
const mutations = {
  addToCart (state, product) {
    ...
  },
  deleteFromCart (state, prodId) {
    ...
  },
  // 改变所有商品的isChecked属性
  // 需要两个参数,第二个是checkbox的状态
  updateAllProductChecked (state, checked) {
    // 给每个商品的isChecked属性为checkbox状态
    state.cartProducts.forEach(prod => {
      prod.isChecked = checked
    })
  },
  // 改变某个商品的isChecked属性
  // 需要两个属性,第二个是商品对象,这里是解构,一个是checked,一个是id
  updateProductChecked (state, {
    checked,
    prodId
  }) {
    // 找到对应id的商品对象
    const prod = state.cartProducts.find(item => item.id === prodId)
    // 如果商品对象存在就给其isChecked进行赋值
    prod && (prod.isChecked = checked)
  }
}
  1. views/cart.vue中进行引入修改
  • 引入mutation
  • 找到父checkbox绑定计算属性
  • 定义checkbox计算属性,完成getset
  • checkbox中使用
<template>
  <div>
    ...
    <el-table
      :data="cartProducts"
      style="width: 100%"
    >
      <el-table-column
        width="55">
        <template v-slot:header>
          <!-- 2. 这里绑定一个v-model,计算属性 -->
          <el-checkbox size="mini" v-model="checkedAll">
          </el-checkbox>
        </template>
         <!-- 4. 这里不能直接绑定v-model,因为我们绑定的是vuex的状态,不能直接更改状态
            4.1 先绑定其isChecked属性
            4.2 注册改变事件change,当checkbox改变的时候调用change,接收两个参数,id就通过scope.row获取,checked状态就通过$event获取 -->
        <template v-slot="scope">
          <el-checkbox
            size="mini"
            :value="scope.row.isChecked"
            @change="updateProductChecked({
              prodId: scope.row.id,
              checked: $event
            })"
          >
          </el-checkbox>
        </template>
      </el-table-column>
      ...
    </el-table>
    ...
  </div>
</template>

<script>
import { mapMutations, mapState } from 'vuex'
export default {
  name: 'Cart',
  computed: {
    ...mapState('cart', ['cartProducts']),
    // 3. 父checkbox的状态,因为有get和set所以直接写成对象形式
    checkedAll: {
      // 返回当前购物车的商品是否都是选中状态,如果有一个没有选中直接返回false
      get () {
        return this.cartProducts.every(prod => prod.isChecked)
      },
      // 状态改变的时候触发的方法,需要一个参数,checkbox的状态
      set (value) {
        this.updateAllProductChecked(value)
      }
    }
  },
  methods: {
    // 1. 将cart模块的mutations映射到methods
    ...mapMutations('cart', ['updateAllProductChecked', 'updateProductChecked'])
  }
}
</script>

<style></style>
  1. 打开浏览器,选中商品进入购物车,可以对全选框进行点击

数字加减并统计小计

  1. cart模块中,定义一个mutation方法,更新商品
const mutations = {
  ...
  // 更新商品,把商品id和count进行解构
  updateProduct (state, { prodId, count }) {
    // 找到当前商品
    const prod = state.cartProducts.find(prod => prod.id === prodId)
    // 如果找到了就更新数量和总价
    if (prod) {
      prod.count = count
      prod.totalPrice = count * prod.price
    }
  }
}
  1. cart.vue中添加一个mapMutations
<script>
...
export default {
  ...
  methods: {
    // 将cart模块的mutations映射到methods
    ...mapMutations('cart', [
      'updateAllProductChecked',
      'updateProductChecked',
      'updateProduct'
    ])
  }
}
</script>
  1. 在数字框中进行方法绑定
<el-table-column
    prop="count"
    label="数量">
    <!-- 这里先定义一个插槽,绑定value是count,定义一个改变的change方法,将updateProduct传入两个参数,一个是id,一个是当前input的值$event -->
    <template v-slot="scope">
      <el-input-number :value="scope.row.count" @change="updateProduct({
        prodId: scope.row.id,
        count: $event
      })" size="mini"></el-input-number>
    </template>
  </el-table-column>
  1. 在浏览器中查看,添加商品之后,修改数字,会有对应的商品数量和小计

删除功能

  1. 之前已经在cart.js的模块中有了删除商品的mutation,这里直接使用,在cart.vue中添加
<script>
...
export default {
  ...
  methods: {
    // 将cart模块的mutations映射到methods
    ...mapMutations('cart', [
      'updateAllProductChecked',
      'updateProductChecked',
      'updateProduct',
      'deleteFromCart'
    ])
  }
}
</script>
  1. 在上面的删除按钮中定义方法
<el-table-column
    label="操作">
    <!-- 定义一个插槽,删除按钮绑定事件,传入商品id -->
    <template v-slot="scope">
    <el-button size="mini"
        @click="deleteFromCart(scope.row.id)">删除</el-button>
    </template>
</el-table-column>
  1. 浏览器中,添加商品之后进入购物车页面,点击删除按钮可以删除整个商品。

统计总数量和总钱数

统计的过程中需要添加条件,判断当前商品是否是选中状态。

  1. cart.jsgetters中添加商品数量和总价的方法,并且对选中状态进行判断
const getters = {
  totalCount (state) {
    ...
  },
  totalPrice () {
    ...
  },
  // 选中的商品数量
  checkedCount (state) {
    // 返回前判断是否是选中状态,如果是就进行添加,并且返回sum
    return state.cartProducts.reduce((sum, prod) => {
      if (prod.isChecked) {
        sum += prod.count
      }
      return sum
    }, 0)
  },
  // 选中的商品价格,同理上面
  checkedPrice () {
    return state.cartProducts.reduce((sum, prod) => {
      if (prod.isChecked) {
        sum += prod.totalPrice
      }
      return sum
    }, 0)
  }
}
  1. cart.vue中导入mapGetters
<script>
import { mapGetters, mapMutations, mapState } from 'vuex'
export default {
  name: 'Cart',
  computed: {
    ...mapState('cart', ['cartProducts']),
    // 将cart模块中的getters映射到computed中
    ...mapGetters('cart', ['checkedCount', 'checkedPrice']),
    ...
  },
  ...
}
</script>
  1. 在总价格处引用
<div>
  <p>已选 <span>{{ checkedCount }}</span> 件商品,总价:<span>{{ checkedPrice }}</span></p>
  <el-button type="danger">结算</el-button>
</div>

处理金额小数的问题

多添加商品的时候发现商品金额会出现很多位小数的问题,所以这里进行处理

  1. mutations中会价格的乘积进行保留两位小数的操作
const mutations = {
  // 添加商品
  addToCart (state, product) {
    const prod = state.cartProducts.find(item => item.id === product.id)
    if (prod) {
      prod.count++
      prod.isChecked = true
      // 小计 = 数量 * 单价
      prod.totalPrice = (prod.count * prod.price).toFixed(2)
      console.log(prod.totalPrice)
    } else {
      ...
    }
  },
  // 更新商品
  updateProduct (state, { prodId, count }) {
    const prod = state.cartProducts.find(prod => prod.id === prodId)
    if (prod) {
      prod.count = count
      // 保留两位小数
      prod.totalPrice = (count * prod.price).toFixed(2)
    }
  }
}
  1. getters中将总价进行保留两位小数,记得转化成数字
const getters = {
  // 价格总计
  totalPrice () {
    return state.cartProducts.reduce((sum, prod) => sum + Number(prod.totalPrice), 0).toFixed(2)
  },
  // 选中的商品价格
  checkedPrice () {
    return state.cartProducts.reduce((sum, prod) => {
      if (prod.isChecked) {
        sum += Number(prod.totalPrice)
      }
      return sum
    }, 0).toFixed(2)
  }
}

本地存储

刷新页面,购物车的数据就会消失,因为我们把数据添加到了内存中存储,而实际购物的时候,有两种存储方式:

  • 如果用户登录,购物车的数据是在服务器中
  • 如果用户没有登录,购物车的数据是存在本地存储中

现在实现本地存储的功能

  1. 首先在cart.js中,首次进入界面的时候,从本地获取数据
const state = {
  // 从本地获取购物车商品数据,如果没有初始化为空数组
  cartProducts: JSON.parse(window.localStorage.getItem('cart-products')) || []
}
  1. mutations中更改数据,所以每次更改过的数据,都需要记录到本地存储中,这里使用vuex的插件,在index.js
...
Vue.use(Vuex)

const myPlugin = store => {
  store.subscribe((mutation, state) => {
    // mutation 的格式为 { type, payload }
    // type里面的格式是cart/cartProducts
    // state 的格式为 { cart, products }
    if (mutation.type.startsWith('cart/')) {
      // 本地存储cartProducts
      window.localStorage.setItem('cart-products', JSON.stringify(state.cart.cartProducts))
    }
  })
}
export default new Vuex.Store({
  ...
  // 将myPlugin挂载到Store上
  plugins: [myPlugin]
})
  1. 刷新浏览器可以看到购物车的商品列表的数据还存在。

完整案例

vuex-cart-temp

查看原文

赞 1 收藏 0 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-31

Vuex(一) —— 集中式的状态管理仓库

目录

  • Vue组件间通信方式回顾

    • 组件内的状态管理流程
    • 组件间通信方式

      • 父组件给子组件传值 (最简单的一种方式)
      • 子组件给父组件传值
      • 不相关组件之间传值
      • 其他常见方式($ref)
  • 简易的状态管理方案

    • 上面组件间通信方式的问题
    • 集中式的状态管理方案
  • Vuex

    • 什么是Vuex?
    • 什么情况下使用Vuex?

      • 非必要的情况不要使用Vuex
      • 中大型单页应用程序使用更好
    • Vuex核心概念回顾
    • Vuex基本结构
    • State的使用
    • Getter的使用
    • Mutation的使用

      • Mutation的调试

        • 时光旅行
        • 状态回滚
        • 提交改变
    • Actions的使用
    • Modules的使用

      • 模块定义
      • 模块注册
      • 模块使用
      • 添加命名空间
    • Vuex严格模式
    • Vuex插件

      • 插件的使用

年底最后一天来本来没有想更博客,但是留下我今年还在努力学习的印记哈哈。看了半天这居然是我第一个关于vue的博客,努力不算晚。先来对这个博客的内容进行一下梳理,有兴趣的小伙伴可以跳到自己感兴趣的地方,这个博客比较长,长文警告。

Vue组件间通信方式回顾

组件内的状态管理流程

Vue最核心的两个功能:数据驱动组件化

使用基于组件化的开发,可以提高开发效率,带来更好的可维护性。

new Vue({
    // state 组件内部都可以管理自己的内部状态
    data () {
        return {
            count: 0
        }
    },
    // view 视图,每个组件都有自己的视图,把状态绑定到视图上,当用户和视图交互的时候,可能会更改状态
    template: `<div>{{ count }}</div>`,
    // actions 行为,这里描述的是单个组件内部的状态管理,实际开发中可能多个组件可以共享状态
    methods: {
        increment () {
            this.count++
        }
    }
})

这里说的状态管理 —— 是通过状态,集中管理和分发,解决多个组件共享状态的问题。

  • state:状态,数据源。
  • view:视图。通过把状态绑定到视图呈现给用户
  • actions:用户和视图交互改变状态的方式

图中表明,状态绑定到视图上呈现给用户,用户通过与视图交互改变状态,之后改变了的状态再绑定到视图会后呈现给用户。
单向的数据流程很简单清晰,但是多个组件共享数据会破坏这种简单的结构。

组件间通信方式的回顾

大多数情况下,组件都不是孤立存在的,他们需要共同协作构成一个复杂的业务功能,在Vue中,为不同的组件关系提供了不同的通信规则。

常见的组件间通信的方式有:

父组件给子组件传值 (最简单的一种方式)

  • 父组件中给子组件通过相应属性传值
  • 子组件通过props接受数据
<!--子组件-->
<template>
  <div>
    <h1>Props Down Child</h1>
    <h2>{{ title }}</h2>
  </div>
</template>

<script>
export default {
  // 子组件中通过props来接收父组件传的值
  // props可以是数组也可以是对象
  // 如果想约定传值的类型用对象,这里title定了是string类型,如果传number类型会报错
  // props: ['title'],
  props: {
    title: String
  }
}
</script>

<style>

</style>
<!--父组件-->
<template>
  <div>
    <h1>Props Down Parent</h1>
    <!--2. 使用子组件的使用通过属性给子组件传值,这里也可以是表达式,绑定data中的成员-->
    <child title="My journey with Vue"></child>
  </div>
</template>

<script>
import child from './01-Child'
export default {
 // 1. 注册子组件
  components: {
    child
  }
}
</script>

<style>

</style>

子组件给父组件传值

  • 子组件通过自定义事件,用$emit触发的时候携带参数给父组件传值
  • 父组件通过$on注册子组件内部触发的事件,并接收传递的数据,行内可以通过$event获取事件传递的参数 (事件处理函数中是不这么使用的)
<!--子组件-->
<template>
  <div>
    <h1 :style="{ fontSize: fontSize + 'em' }">Props Down Child</h1>
    <button @click="handler">文字增大</button>
  </div>
</template>

<script>
export default {
 // 通过props接收父组件传的默认字体大小
  props: {
    fontSize: Number
  },
  methods: {
    handler () {
    // 当点击按钮的时候,触发自定义事件enlargeText放大字体,让字体放大0.1
    // this是当前子组件对象,this.$emit这个是由子组件触发的自定义事件,当注册事件的时候要给子组件注册该事件
      this.$emit('enlargeText', 0.1)
    }
  }
}
</script>

<style></style>
<!--父组件-->
<template>
  <div>
  <!--父组件将fontSize进行绑定-->
    <h1 :style="{ fontSize: hFontSize + 'em'}">Event Up Parent</h1>

    这里的文字不需要变化
    <!--使用子组件,通过v-on给子组件设置了自定义方法enlargeText-->
    <child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
    <child :fontSize="hFontSize" v-on:enlargeText="enlargeText"></child>
    <!--还有一种在行内获取值的方式,获取自定义事件传递数据的时候,直接通过$event获取,这个值是触发事件传递的0.1-->
    <child :fontSize="hFontSize" v-on:enlargeText="hFontSize += $event"></child>
  </div>
</template>

<script>
import child from './02-Child'
export default {
  components: {
    child
  },
  data () {
    return {
      hFontSize: 1
    }
  },
  methods: {
  // 子组件把值传递给了父组件,父组件通过参数接收到了值,进行运算
    enlargeText (size) {
      this.hFontSize += size
    }
  }
}
</script>

<style></style>

不相关组件之间传值

  • 也是使用自定义事件的方式,但是因为没有父子关系,所以不能通过子组件触发传值,所以这里需要使用eventBus,即创建一个公共的Vue实例,这个实例的作用是作为事件总线,或者事件中心。
  • eventBus:创建一个Vue实例,这个实例并不是用来展示内容,所以没有传递任何的选项,我们使用他的目的是使用$emit$on,用来触发和注册事件。
  • 触发事件组件:通过$emit触发事件,并传递参数
  • 注册事件组件:通过$on注册事件,接收参数进行处理
// eventbus.js
import Vue from 'vue'
export default new Vue()
<!--组件一:触发事件-->
<template>
  <div>
    <h1>Event Bus Sibling01</h1>
    <div class="number" @click="sub">-</div>
    <input type="text" style="width: 30px; text-align: center" :value="value">
    <div class="number" @click="add">+</div>
  </div>
</template>

<script>
// 这个组件要触发事件,将事件中心导入
import bus from './eventbus'

export default {
// props参数num
  props: {
    num: Number
  },
  // 因为props的值不能随便改动,所以传递给value
  created () {
    this.value = this.num
  },
  data () {
    return {
      value: -1
    }
  },
  methods: {
  // 减值操作,判断不能为0
    sub () {
      if (this.value > 1) {
        this.value--
        // 触发bus的自定义事件numchange,并把value当参数传递出去
        bus.$emit('numchange', this.value)
      }
    },
    // 加值操作,和减值类似
    add () {
      this.value++
      bus.$emit('numchange', this.value)
    }
  }
}
</script>

<style>
.number {
  display: inline-block;
  cursor: pointer;
  width: 20px;
  text-align: center;
}
</style>
<!--组件二:定义-->
<template>
  <div>
    <h1>Event Bus Sibling02</h1>

    <div>{{ msg }}</div>
  </div>
</template>

<script>
// 因为要注册事件,所以将事件中心导入
import bus from './eventbus'
export default {
  data () {
    return {
      msg: ''
    }
  },
  created () {
    // 通过bus注册了numchange事件,事件处理函数中接收事件触发时候传递的参数,进行展示
    bus.$on('numchange', (value) => {
      this.msg = `您选择了${value}件商品`
    })
  }
}
</script>

<style>

</style>
<!--App.vue-->
<template>
  <div id="app">
    <h1>不相关组件传值</h1>
    <sibling0301 :num="num"></sibling0301>
    <sibling0302></sibling0302>
  </div>
</template>

<script>
import sibling0301 from './components/03-event-bus/03-Sibling-01'
import sibling0302 from './components/03-event-bus/03-Sibling-02'

export default {
  name: 'App',
  components: {
    sibling0301,
    sibling0302,
  },
  data () {
    return {
      num: 1
    }
  }
}
</script>

<style></style>

其他常见方式

  • $root$parent$children$ref,通过这几种属性获取根组件成员,实现组件之间的通信。但这些都是不被推荐的方式。只有当项目很小,或者在开发自定义组件的时候,才会使用到。如果是大型项目的话,还是推荐使用Vuex管理状态。

下面举例通过$refs获取子组件的状态,其他属性可以自己查看文档。

ref的两个作用

  1. 在普通HTML标签上使用ref,用$refs获取到的是DOM对象
  2. 在组件标签上使用ref,用$refs获取到的是组件实例
<!--子组件,一个简单的自定义组件,功能是能够获取焦点的自定义文本框。-->
<template>
  <div>
    <h1>ref Child</h1>
    <!--这个input标签上设置了ref属性-->
    <input ref="input" type="text" v-model="value">
  </div>
</template>

<script>
export default {
  data () {
    return {
      value: ''
    }
  },
  methods: {
    focus () {
      // 通过this.$refs.input获取input的DOM对象,并调用其focus方法让文本框获取焦点
      this.$refs.input.focus()
    }
  }
}
</script>

<style></style>
<!--父组件,-->
<template>
  <div>
    <h1>ref Parent</h1>
    <!--在子组件的标签上设置了ref-->
    <child ref="c"></child>
  </div>
</template>

<script>
import child from './04-Child'
export default {
  components: {
    child
  },
  mounted () {
    // 这里想要拿到子组件的话,必须要等组件渲染完毕,所以这里在mounted函数下
    // 这里通过this.$refs.c就是子组件对象,拿到这个对象就可以访问其属性和方法
    // 这里调用子组件方法让其内部获取焦点
    this.$refs.c.focus()
    // 通过value属性给文本框赋值
    this.$refs.c.value = 'hello input'
  }
}
</script>

<style>

</style>

还是一句话不建议使用,如果滥用这种方式的话可以造成状态管理的混乱。

简易的状态管理方案

上面组件间通信方式的问题

如果多个组件之间需要共享状态,使用之前演示的方式虽然都可以实现,但是比较麻烦,而且多个组件之间进行传值,很难跟踪到数据的变化。如果出现问题的话,很难定位问题。当遇到多个组件需要共享状态的时候,典型的场景如购物车,我们使用之前介绍的方案都不合适,可能会遇到以下的问题:

  • 多个视图依赖同一状态,如果多层嵌套的组件依赖同一状态,使用父子组件传值可以实现,但是非常麻烦而且不易管理。
  • 来自不同视图的行为需要变更同一状态,我们可以通过父子组件的方式对状态进行修改,或者通过事件机制来改变,或者同步状态的变化,以上这些方式非常的脆弱,通常会导致产生无法维护的代码。

集中式的状态管理方案

为了解决这些问题,我们把不同组件的共享状态抽取出来,存储到一个全局对象中并且将来使用的时候保证其实响应式的。这个对象创建好之后里面有全局的状态和修改状态的方法,我们的任何组件都可以获取和通过调用对象中的方法修改全局对象中的状态 (组件中不允许直接修改对象的state状态属性)。

把多个组件的状态放到一个集中的地方存储,并且可以检测到数据的更改,这里先不使用Vuex,我们自己先进行一个简单的实现。

  1. 创建一个全局的store.js
集中式的状态管理,所有的状态都在这里。这个模块中导出了一个对象,这对象就是状态仓库且全局唯一的对象,任何组件都可以导入这个模块使用

这里面有state,还有actionsstate是用来存储状态,actions是用户交互更改视图用的。还有一个debug的属性,方便开发调试。

// store.js
export default {
  debug: true,
  state: {
    user: {
      name: 'xiaomao',
      age: 18,
      sex: '男'
    }
  },
  setUserNameAction (name) {
    if (this.debug) {
      console.log('setUserNameAction triggered:', name)
    }
    this.state.user.name = name
  }
}
  1. 在组件中导入
<!--组件A-->
<template>
  <div>
    <h1>componentA</h1>
    <!--3. 可以在视图中直接用点的方式显示数据-->
    user name: {{ sharedState.user.name }}
    <button @click="change">Change Info</button>
  </div>
</template>

<script>
// 1. 在组件中导入store
import store from './store'
export default {
  methods: {
    // 4. 当点击按钮的时候,调用store的方法,将值改为componentA
    change () {
      store.setUserNameAction('componentA')
    }
  },
  data () {
    return {
      // 当前组件还可以有自己的私有状态,存在privateState中
      privateState: {},
      // 2. 将store的state属性赋值给shareState
      sharedState: store.state
    }
  }
}
</script>

<style></style>
<!--组件B,用法与上面一样,就是修改名字的时候值为componentB-->
<template>
  <div>
    <h1>componentB</h1>
    user name: {{ sharedState.user.name }}
    <button @click="change">Change Info</button>
  </div>
</template>

<script>
import store from './store'
export default {
  methods: {
    change () {
      // 修改名字的时候改成了componentB
      store.setUserNameAction('componentB')
    }
  },
  data () {
    return {
      privateState: {},
      sharedState: store.state
    }
  }
}
</script>

<style></style>

上面组件A和组件B都共享了全局的状态,并且用户都可以更改状态。调试的时候,按A组件的按钮两者都变成了componentA,点B组件的按钮两者都变成了componentB

我们不在组件中直接修改状态的值而是通过调用storeactions来修改值,这样记录的好处是: 能够记录store中所以state的变更,当可以实现记录storestate的变更时候,就可以实现高级的调试功能。例如:timeTravel(时光旅行)和历史回滚功能。

刚才使用的store,其实就类似于Vuex的仓库。

当项目比较复杂,多个组件共享状态的时候,使用组件间通信的方式比较麻烦,而且需要维护。这个时候我们可以使用集中式的状态解决方案 —— Vuex

Vuex

好的终于进入了主题~~

什么是Vuex?

  • Vuex官网
  • Vuex 是专门为 Vue.js 设计的状态管理库,从使用的角度其实就是一个JavaScript
  • 它采用集中式的方式存储需要共享的数据,如果状态特别多的话不易管理,所以Vuex还提供了一种模块的机制,按照模块管理不同的状态
  • 它的作用是进行状态管理,解决复杂组件通信,数据共享
  • Vuex 也集成到 Vue 的官方调试工具 devtools extension,提供了time-travel时光旅行、历史回滚、状态快照、导入导出等高级调试功能

什么情况下使用Vuex?

非必要的情况不要使用Vuex

Vuex 可以帮助我们管理组件间共享的状态,但是在项目中使用Vuex的话,我们需要了解Vuex中带来的新的概念和一些API,如果项目不大,并且组件间共享状态不多的情况下,这个时候使用Vuex给我们带来的益处并没有付出的时间多。此时使用简单的 store 模式 或者其他方式就能满足我们的需求。

中大型单页应用程序使用更好

中大型单页应用程序中,使用Vuex可以帮我们解决多个视图依赖同一状态来自不同视图的行为需要变更同一状态的问题。建议符合这些情况的业务,使用Vuex进行状态管理,会给我们提供更好的处理组件的状态,带来的收益会更好些。例如典型案例:购物车。

注意:不要滥用Vuex,否则会让业务变得更复杂。

Vuex核心概念回顾

下面这张图展示了Vuex的核心概念并且展示了Vuex的整个工作流程

  • Store:仓库,Store是使用Vuex应用程序的核心,每个应用仅有一个Store,它是一个容器,包含着应用中的大部分状态,当然我们不能直接改变Store中的状态,我们要通过提交Mutations的方式改变状态。
  • State:状态,保存在Store中,因为Store是唯一的,所以State也是唯一的,称为单一状态树,这里的状态是响应式的。
  • Getter:相当于Vuex中的计算属性,方便从一个属性派生出其他的值,它内部可以对计算的结果进行缓存,只有当依赖的状态发生改变的时候,才会重新计算。
  • Mutation:状态的变化必须要通过提交Mutation来完成。
  • Actions:与Mutation类似,不同的是可以进行异步的操作,内部改变状态的时候都需要改变Mutation
  • Module:模块,由于使用的单一状态树让所有的状态都会集中到一个比较大的对象中,应用变得很复杂的时候,Store对象就会变得相当臃肿,为了解决这些问题Vuex允许我们将Store分割成模块,每个模块拥有自己的StateMutationActionsGetter,甚至是嵌套的子模块。

Vuex基本结构

使用vue-cli创建项目的时候,如果选择了Vuex,会自动生成Vuex的基本结构。

// store.js
import Vue from 'vue'
// 导入Vuex插件
import Vuex from 'vuex'
// 通过use方法注册插件
// 插件内部把Vuex的Store注入到了Vue的实例上
Vue.use(Vuex)
// 创建了Vuex的Store对象并且导出
export default new Vuex.Store({
    state: {
        ...
    },
    mutations: {
        ...
    },
    actions: {
        ...
    },
    modules: {
        ...
    }
    // 如果有需要还可以有getters
})
//App.js
// 导入store对象
import store from './store'
new Vue({
    router,
    // 在初始化Vue的时候传入store选项,这个选项会被注入到Vue实例中
    // 我们在组件中使用的this.$store就是在这个地方注入的
    store,
    render: h => h(App)
}).$mount('#app')

State的使用

  1. 下载项目模板vuex-sample-tempnpm install下载依赖,在store/index.js中定义两个state
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'hello vue'
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
  1. App.vue中使用state,然后使用npm run serve查看结果
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count: {{ $store.state.count}} <br/>
    msg: {{ $store.state.ms }}
  </div>
</template>
<script>
  1. 每次使用变量都要前面写 $store.state 很是麻烦,所以这里使用`Vuex内部提供的myState`的函数,会帮我们生成状态对应的计算属性
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <!--4. 在使用的时候直接用计算属性count和msg即可-->
    count: {{ count }} <br/>
    msg: {{ msg }}
  </div>
</template>
<script>
  // 1. 引入vuex的mapState模块
  import { mapState } from 'vuex'
  export default {
    // 2. 在计算属性中调用mapState函数
    computed: {
      // 3. mapState需要接收数组作为参数,数组的元素是需要映射的状态属性
      // 会返回一个对象,包含两个对应属性计算的方法
      // { count: state => state.count, msg: state => state.msg }
      // 然后这里使用扩展运算符展开对象,完成之后我们就有了count和msg两个计算属性
      ...mapState(['count', 'msg'])
    }
  }
</script>
  1. 上面的方法比较简洁但是如果这个组件中本身就有count或者msg属性,就会造成名称冲突,这个时候需要设置别名。
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <!-- 使用的时候直接使用别名即可 -->
    count: {{ num }} <br/>
    msg: {{ message }}
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  computed: {
    // mapState可以传对象,键是别名,值是映射的状态属性
    ...mapState({ num: 'count', message: 'msg' })
  }
}
</script>

Getter的使用

Vuex中的getter就相当于组件中的计算属性,如果想要对state的数据进行简单的处理在展示,可以使用getter

这里用Vuexgetter处理而不是用组件中的计算属性是因为状态本身属于Vuex,应该在其内部处理
  1. store.js中设置getters
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'hello vue'
  },
  // 与计算属性的写法一致
  getters: {
    reverseMsg (state) {
      return state.msg.split('').reverse().join('')
    }
  },
  mutations: {
  },
  actions: {
  },
  modules: {
  }
})
  1. App.vue中使用
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <h2>reverseMsg: {{ $store.getters.reverseMsg }}</h2>
    <br/>
  </div>
</template>
  1. 同样那样引用过于麻烦,那么和mapState一样,使用内部的mapGetters,也是将其映射到组件的计算属性,其用法和mapState一样,也可以为了避免冲突使用对象设置别名。
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <h2>reverseMsg: {{ reverseMsg }}</h2>
    <br/>
  </div>
</template>
<script>
// 1. 引入vuex的mapGetters模块
import { mapGetters } from 'vuex'
export default {
  // 2. 在计算属性中调用mapGetters函数
  computed: {
    // 3. 用法与mapState一致,这里也可以使用对象设置别名
    ...mapGetters(['reverseMsg'])
  }
}
</script>

Mutation的使用

状态的修改必须提交MutationMutation必须是同步执行的。

  1. 当用户点击按钮的时候,count值进行增加,先在store.js中写Mutation函数
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'hello vue'
  },
  mutations: {
    // 增加函数,接收两个参数
    // 第一个state状态
    // 第二个是payload载荷,payload是mutations的时候提交的额外参数,可以是对象,这里传递的是数字
    increate (state, payload) {
      state.count += payload
    }
  },
  actions: {
  },
  modules: {
  }
})
  1. App.vue中设置按钮,并注册事件
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count: {{ $store.state.count }} <br/>
    <h2>Mutation</h2>
    <!-- 给按钮注册点击事件,点击的时候调用commit提交Mutation,第一个参数是调用的方法名,第二个参数是payload,传递的数据 -->
    <button @click="$store.commit('increate', 2)">Mutation</button>
  </div>
</template>
  1. 点击按钮的时候,count的值每次+2
  2. 下面进行写法优化,使用map方法将当前的mutation映射到methods中,其依旧会返回一个对象,这个对象中存储的是mutation中映射的方法
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count: {{ $store.state.count }} <br/>
    <h2>Mutation</h2>
    <!-- 这里直接写方法,,第一个参数state不需要传递,后面传payload参数为3 -->
    <button @click="increate(3)">Mutation</button>
  </div>
</template>
<script>
// 1. 引入vuex的mapMutations模块
import { mapMutations } from 'vuex'
export default {
  // 2. methods中调用mapMutations方法
  methods: {
    ...mapMutations(['increate'])
  }
}
</script>

Mutation的调试

运行到4之后,这时看一下devtools看一下时光旅行和历史回滚,下面是初始状态

点一下按钮之后就增加了一个记录,还显示了改变之后的数据

如果数据不对,可以进行调试。

时光旅行

然后多点几下,进行时光旅行。

点击按钮之后,状态就变成了之前那个状态,这个功能也是为了方便调试

状态回滚

这个图标就是状态回滚

点击之后,代码就回到了没有执行这一步的状态

提交改变

下面那个按钮的意思是将这次提交作为最后一次提交

点击之后,base State变成了那次的状态,其他的状态以这个作为起始点

Actions的使用

如果有异步的修改,需要使用actions,在actions中可以执行异步操作,当异步操作结束后,如果需要更改状态,还需要提交Mutation

  1. actions中添加方法increateAsync
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0,
    msg: 'hello vue'
  },
  mutations: {
    increate (state, payload) {
      state.count += payload
    }
  },
  actions: {
    // actions中的方法有两个参数:第一个参数是context上下文,这个对象中有state,commit,getters等成员,第二个参数是payLoad
    increateAsync (context, payLoad) {
      setTimeout(() => {
        context.commit('increate', payLoad)
      }, 2000)
    }
  },
  modules: {
  }
})
  1. App.vue中使用dispatchactions的方法都要用这个
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count: {{ $store.state.count }} <br/>
    <h2>Actions</h2>
    <!--这里使用了dispatch-->
    <button @click="$store.dispatch('increateAsync',5)">Action</button>
  </div>
</template>
  1. 进行优化,这个时候引入mapActions
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    count: {{ $store.state.count }} <br/>
    <h2>Actions</h2>
    <button @click="increateAsync(5)">Action</button>
  </div>
</template>
<script>
// 1. 引入vuex的mapActions模块
import { mapActions } from 'vuex'
export default {
  methods: {
    // 这个是对Actions的方法的映射,把this.increateAsync映射到this.$store.dispatch
    ...mapActions(['increateAsync'])
  }
}
</script>

Modules的使用

模块可以让我们把单一状态树拆分成多个模块,每个模块都可以拥有自己的statemutationactiongetter甚至嵌套子模块。

模块定义

store文件夹中,创建一个modules文件夹,里面每一个js文件就是一个模块,下面是每一个模块的定义格式

const state = {
  products: [
    { id: 1, title: 'iPhone 11', price: 8000 },
    { id: 2, title: 'iPhone 12', price: 10000 }
  ]
}
const getters = {}
const mutations = {
  setProducts (state, payload) {
    state.products = payload
  }
}
const actions = {}

export default {
  namespaced: false,
  state,
  getters,
  mutations,
  actions
}

模块注册

  1. 先导入这个模块
import products from './modules/products'
import cart from './modules/cart'
  1. 后来在modules选项中注册,注册之后这里会把模块挂载到storestate中,这里可以通过store.state.products访问到products模块中的成员,还把的模块中的mutation成员记录到了store的内部属性_mutation中,可以通过commit直接提交mutation
export default new Vuex.Store({
  state: {},
  getters: {},
  mutations: {},
  actions: {},
  modules: {
    products,
    cart
  }
})

模块使用

  1. App.vue中使用,state就点出来,mutation还是用commit方法
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <h2>Modules</h2>
    <!--第一个products是products模块,第二个products是模块的state的products属性-->
    products: {{ $store.state.products.products }} <br/>
    <button @click="store.commit('setProducts',[])">Mutation</button>
  </div>
</template>

添加命名空间

因为每个模块中的mutation是可以重名的,所以推荐使用命名空间的用法,方便管理。

  1. 在开启命名空间的时候,在模块的导出部分添加namespaced
const state = {}
const getters = {}
const mutations = {}
const actions = {}

export default {
  // true就是开启,false或者不写就是关闭
  namespaced: false,
  state,
  getters,
  mutations,
  actions
}
  1. 使用的时候在App.vue中要设置state是模块中出来的,如果没有命名空间,就是全局的state的。
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    products: {{ products }} <br/>
    <button @click="setProducts([])">Mutation</button>
  </div>
</template>
<script>
import { mapState, mapMutations } from 'vuex'
export default {
  computed: {
    // 模块中的state,第一个参数写模块名称,第二个参数写数组或者对象
    ...mapState('products', ['products'])
  },
  methods: {
    // 模块中的mutations,第一个写模块名称,第二个写数组或者对象
    ...mapMutations('products', ['setProducts'])
  }
}
</script>

Vuex严格模式

所有的状态变更必须提交mutation,但是如果在组件中获取到$store.state.msg进行修改,语法层面没有问题,却破坏了Vuex的约定,且devTools也无法跟踪到状态的修改,开启严格模式之后,如果在组件中直接修改state,会报错。

  1. index.js,初始化Store的时候开启严格模式
export default new Vuex.Store({
  strict: true,
  state: {
    ...
  },
  ...
}
  1. App.vue中使用直接赋值的语句
<template>
  <div id="app">
    <h1>Vuex - Demo</h1>
    <h2>strict</h2>
    <button @click="$store.state.msg = 'hello world~'">strict</button>
  </div>
</template>
  1. 点击按钮内容改变,但是控制台会抛出错误

注意:不要在生产环境开启严格模式,因为严格模式会深度检测状态树,会影响性能。在开发模式中开启严格模式,在生产环境中关闭严格模式

export default new Vuex.Store({
 strict: process.env.NODE_ENV !== 'production',
 state: {
  ...
}

Vuex插件

  • Vuex插件就是一个函数,接收一个store的参数
  • 在这个函数中可以注册函数让其在所有的mutations结束之后再执行

插件的使用

  • 插件应该在创建Store之前去创建
  • subscribe函数

    • 作用是去订阅store中的mutation
    • 他的回调函数会在每个mutation之后调用
    • subscribe会接收两个参数,第一个是mutation,还可以区分模块的命名空间,第二个参数是state,里面是存储的状态
  1. 定义插件
// 这个函数接收store参数
const myPlugin = store => {
    // 当store初始化后调用
    store.subscribe((mutation, state) => {
        // 每次 mutation 之后调用
        // mutation 的格式为 { type, payload }
        // type里面的格式是 "模块名/state属性"
        // state 的格式为 { 模块一, 模块二 }
    })
}
  1. Store中注册插件
const store = new Vuex.Store({
    //...
    plugins: [myPlugin]
})
查看原文

赞 1 收藏 0 评论 0

顽皮的雪狐七七 关注了专栏 · 2020-12-27

范畴论与Haskell

关于范畴论文章的翻译和一些函数式编程的心得

关注 16

顽皮的雪狐七七 发布了文章 · 2020-12-27

Plop —— 小而美的脚手架工具

目录

  • Plop介绍
  • Plop的具体使用

    • 具体步骤
  • 总结

Plop介绍

主要用于创建项目中特定文件类型的小工具,类似于Yeoman中的sub generator,一般不会独立使用。一般会把Plop集成到项目中,用来自动化的创建同类型的项目文件。

Plop的具体使用

具体步骤

  1. 新建目录,初始化npm init -y,安装Plop
npm install -g plop
  1. 在目录下创建plop-templates文件夹,里面创建三个模板文件
  • component.css.hbs
.{{name}} {
  
}
  • component.hbs
import React from 'react';

export default () => (
  <div className="{{ name }}">
    <h1>{{name}} Component</h1>
  </div>
)
  • component.test.hbs
import React from 'react';
import ReactDOM from 'react-dom';
import {{name}} from './{{name}}';

it('renders widthout crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<{{name}} />, div);
  ReactDOM.unmountComponentAtNode(div);
});
  1. 在根目录下创建一个plopfile.js的文件,这个文件是Plop的入口文件,需要导出一个函数,这个函数接收一个plop对象,用于创建生成器任务。
  • plop.setGenerator:设置一个生成器,第一个参数是项目名称,第二个函数是对象,对应设置选项
  • 配置项内容:

    • description:描述
    • prompts:值是数组,命令行交互问题,一个问题对应一个对象
    • actions:值是数组,完成命令行交互过后完成的一些动作,一个对象一个动作
module.exports = plop => {
  // 设置一个生成器,第一个参数是项目名称,第二个函数是对象,对应设置选项
  plop.setGenerator('compontent', {
    // 描述
    description: 'create a component',
    // 命令行交互问题
    prompts: [
        // 一个问题对应一个对象,配置参考自定义Generator
      {
        type: 'input',
        name: 'name',
        message: 'component name',
        default: 'MyComponent'
      }
    ],
    // 完成命令行交互过后完成的一些动作
    actions: [
      //每一个对象都是一个动作
      {
        type: 'add', // 代表添加文件
        // 被添加的文件在输出的哪个路径,双花括号插值表达式可以获取交互得到的数据
        path: 'src/components/{{name}}/{{name}}.js',
        // 模板文件是什么
        templateFile: 'plop-templates/component.hbs'
      },
      {
        type: 'add',
        path: 'src/components/{{name}}/{{name}}.css',
        templateFile: 'plop-templates/component.css.hbs'
      },
      {
        type: 'add',
        path: 'src/components/{{name}}/{{name}}.test.js',
        templateFile: 'plop-templates/component.test.hbs'
      }
    ]
  })
}
  1. package.json中添加
  "scripts": {
    "plop": "plop"
  }
  1. 运行
npm run plop
# 输入模块名称
? component name Header
#√  ++ \src\components\Header\Header.js
#√  ++ \src\components\Header\Header.css
#√  ++ \src\components\Header\Header.test.js

此时在根目录的src\components下面,有了三个文件

  • Header.js
import React from 'react';

export default () => (
  <div className="Header">
    <h1>Header Component</h1>
  </div>
)
  • Header.css
.Header {
  
}
  • Header.test.js
import React from 'react';
import ReactDOM from 'react-dom';
import Header from './Header';

it('renders widthout crashing', () => {
  const div = document.createElement('div');
  ReactDOM.render(<Header />, div);
  ReactDOM.unmountComponentAtNode(div);
});

这样,就可以根据模板一键生成一个组件目录。

总结

  1. plop模块作为项目开发依赖安装
  2. 编写用于生成特定类型文件的模板
  3. 在项目根目录下创建一个plopfile.js文件
  4. plopfile.js文件中定义脚手架任务
  5. 通过Plop提供的CLI运行脚手架任务
查看原文

赞 0 收藏 0 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-26

Yeoman —— 通用脚手架工具

目录

  • Yeoman

    • 优点 & 缺点
    • 安装起步
    • 基本使用
    • sub generator

      • 实例:将项目变成cli项目
      • 使用步骤总结
    • 自定义Generator

      • Generator基本结构
      • 名称规范
      • 实践操作
      • 根据模板创建文件
      • 动态接收用户输入数据
      • 自定义一个带有一定基础代码的vue项目脚手架
      • 发布Generator

Yeoman

一个通用的脚手架工具。

优点 & 缺点

优点 & 缺点内容
优点更像脚手架的运行平台,Yeoman搭配不同的generator可以创建任何类型的项目,我们可以根据自己的generator定制自己的前端脚手架
缺点优点即缺点,过于通用不够专注

安装起步

yarn安装

# 安装yarn工具进行安装
npm install -g yarn
# 查看yarn是否安装好
yarn -v
# 1.22.5
# 全局安装yeoman
yarn global add yo
# 搭配使用node的generator才算安装完毕
yarn global add generator-node

npm安装

npm install -g yo
npm install -g generator-node

基本使用

yo node

会出现下面的提问

# 模块名称
? Module Name my_modules
# (node:13036) [DEP0066] DeprecationWarning: OutgoingMessage.prototype._headers is deprecated
# 已经在npm上存在,是否选择别的?
? The name above already exists on npm, choose another? No
# 描述
? Description node_modules
# 工程主页
? Project homepage url https://gitee.com/burningQiQi/
# 作者名称
? Authors Name csf
# 作者邮箱
? Authors Email shuangfeng1993@163.com
# 作者主页
? Authors Homepage https://gitee.com/burningQiQi/
# 关键词
? Package keywords (comma to split) node,modules,yeoman
# 是否发送代码覆盖率到一个平台上?
? Send coverage reports to coveralls No
# 使用node版本,不写就是所有的
? Enter Node versions (comma separated) 
# GitHub名称和组织者
? GitHub username or organization csf
# 项目license
? Which license do you want to use? MIT
#    create package.json
#     force .yo-rc.json
#     force C:\Users\韵七七\.yo-rc-global.json
#    create README.md
#    create .editorconfig
#    create .gitattributes
#    create .gitignore
#    create .travis.yml
#    create .eslintignore
#    create lib\index.js
#    create LICENSE
#    create lib\__tests__\myModules.test.js

安装完成之后,项目目录中自动就有了下面的配置文件

sub generator

有时候我们并不需要创建完整的项目结构,只需要在原有项目的基础上创建一些特定的文件,例如在项目中添加yeoman,比如在项目中添加eslintbabel配置文件。

我们可以通过生成器帮我们实现

实例:将项目变成cli项目

在上面创建项目的基础上,下面举例我们通过node下面的cli生成器帮我们生成一些cli的文件,把模块变成cli应用

yo node:cli

# > conflict package.json
# 询问我们是不是要重写package.json文件,我们添加cli的时候会有新的模块和依赖,选择yes
# > ? Overwrite package.json? overwrite
# 帮我们重写了package.json并且创建了一个cli.js的文件
#     force package.json
#     create lib\cli.js

然后可以看到package.json中有了cli的相应配置

我们就可以用名称当做全局的命令行模块使用了。

# 将 npm模块/yarn模块 链接到对应的运行项目中去,方便地对模块进行调试和测试
npm link / yarn link
# 下面运行成功说明,cli应用可以正常的工作了
my_modules --help
# node_modules

#   Usage
#     $ my_modules [input]

#   Options
#     --foo  Lorem ipsum. [Default: false]

#   Examples
#     $ my_modules
#     unicorns
#     $ my_modules rainbows
#     unicorns & rainbows
上面只是cli的,还可以安装别的 generator-node

并不是所有的generator都提供子集生成器,需要通过官方文档确定

使用步骤总结

  1. 明确需求
  2. 找到合适的Generatoryeoman官网

  1. 全局范围安装找到的Generator
  2. 通过Yo运行对应的Generator
  3. 通过命令行交互填写选项
  4. 生成你所需要的项目结构

自定义Generator

基于Yeoman搭建自己的脚手架。

Generator基本结构

 𠃊 generators/  ...  生成器目录
 |   𠃊 app/ ... 默认生成器目录
 |   |     𠃊 index.js ... 默认生成器实现
+|   𠃊 component/  ... 如果有sub generator写这个目录下面
+|         𠃊 index.js ... 其他生成器实现
 𠃊 package.json ... 模块包配置文件

名称规范

必须是generator-<name> 的格式

实践操作

  1. 安装Generator生成器
# 创建并进入目录
mkdir generator-sample
cd generator-sample
npm init
# 安装的这个模块提供了生成器的基类,基类中提供了一些工具函数,让我们在创建生成器的时候更加的便捷。
npm install yeoman-generator
  1. 编写index.js核心文件
# 当前在generator-sample文件夹中,创建app文件夹
mkdir app
cd app

app文件夹中创建index.js文件,里面写

/**
 * 此文件作为 Generator 的核心入口
 * 需要导出一个继承自 Yeoman Generator 的类型
 * Yeoman Generator 在工作时会自动调用我们在此类型中定义的一些生命周期方法
 * 我们在这些方法中可以通过调用父类提供的一些工具方法实现一些功能,例如文件写入
 */

 const Generator = require('yeoman-generator')

 module.exports = class extends Generator {
   writing () {
     // Yeoman 自动在生成文件阶段调用此方法
     // 我们这里尝试往项目目录中写入文件
     this.fs.write(
       this.destinationPath('temp.txt'),
       Math.random().toString()
     )
   }
 }
  1. 然后用npm link将项目弄到全局
  2. 之后在别的项目中开始启用
mkdir myjob
cd myjob
yo sample

就可以看到有对应的文件生成。

根据模板创建文件

相对于手动创建每一个文件,模板的方式大大提高了效率

  1. app目录下面创建templates文件夹,里面添加一个foo.txt的模板文件
这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如: <%= title %>

<% if (success) {%>
哈哈哈
<% }%>
  1. app下面的index.js文件进行下面的修改
 const Generator = require('yeoman-generator')
 
 module.exports = class extends Generator {
   writing () {
    // 使用模板方式写入文件到目标目录
    
    // 模板文件路径
    const tmpl = this.templatePath('foo.txt')
    // 输出目标路径
    const output = this.destinationPath('foo.txt')
    // 模板数据上下文
    const context = { title: 'hello xm~', success: true}
    // 这个方法会把模板文件映射到输出文件上
    this.fs.copyTpl(tmpl, output, context)
   }
 }
  1. 运行
cd myjob
yo sample
# create foo.txt

可以看到myjob下面生成了一个foo.txt文件,内容如下:

这是一个模板文件
内部可以使用 EJS 模板标记输出数据
例如: hello xm~


哈哈哈

动态接收用户输入数据

如果我们在命令行中需要动态获取用户输入的数据,可以这样做。

  1. templates中创建一个test.html文件
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title><%= name%></title>
</head>
<body>
  <h1><%= title%></h1>
</body>
</html>
  1. index.js中做如下操作
 const Generator = require('yeoman-generator')

 module.exports = class extends Generator {
   prompting() {
     // Yeoman再次询问用户环节会自动调用此方法
     // 在此方法中可以调用父类的 prompt() 方法发出对用户的命令行询问
     // this.prompt接收一个数组,数组的每一项都是一个问题
     // this.prompt返回一个promise对象
    
     return this.prompt([
      {
        // input 使用用户输入的方式接收提交信息
        type: 'input',
        // 最终得到结果的键
        name: 'name',
        // 给用户的提示
        message: 'your project name is :',
        // 默认值
        default: this.appname // appname 为项目生成目录名称
      },
      {
        type: 'input',
        name: 'title',
        message: 'your title is :',
        default: '目录'
      },
     ])
     .then(answers => {
       // answers是用户输入后我们拿到的一个结果
       // answers => { name: 'user input value', title: 'user input value'}
       // 赋值给属性我们可以在writing中使用它
       this.answers = answers
     })
   }
   writing () {
    // 使用模板方式写入文件到目标目录
    
    // 模板文件路径
    const tmpl = this.templatePath('test.html')
    // 输出目标路径
    const output = this.destinationPath('test.html')
    // 模板数据上下文
    const context = { name: this.answers.name, title: this.answers.title}
    // 这个方法会把模板文件映射到输出文件上
    this.fs.copyTpl(tmpl, output, context)
   }
 }
  1. myjob文件夹下执行
cd myjob
yo sample
> ? your project name is : test myjob
> ? your title is : session1
#create test.html

可以看到生成文件

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>test myjob</title>
</head>
<body>
  <h1>session1</h1>
</body>
</html>

自定义一个带有一定基础代码的vue项目脚手架

  1. 也是在generators里面创建目录结构,然后将整个的vue项目(自己的)放到templates文件夹里面。如同下面:

  1. index.js中进行遍历输出
writing () {
    // 把每一个文件都通过模板转换到目标路径

    const templates = [
      '.browserslistrc',
      '.editorconfig',
      '.env.development',
      '.env.production',
      '.eslintrc.js',
      '.gitignore',
      'babel.config.js',
      'package.json',
      'postcss.config.js',
      'README.md',
      'public/favicon.ico',
      'public/index.html',
      'src/App.vue',
      'src/main.js',
      'src/router.js',
      'src/assets/logo.png',
      'src/components/HelloWorld.vue',
      'src/store/actions.js',
      'src/store/getters.js',
      'src/store/index.js',
      'src/store/mutations.js',
      'src/store/state.js',
      'src/utils/request.js',
      'src/views/About.vue',
      'src/views/Home.vue'
    ]

    templates.forEach(item => {
      // item => 每个文件路径
      this.fs.copyTpl(
        this.templatePath(item),
        this.destinationPath(item),
        this.answers
      )
    })
  }

这样去别的文件夹下执行yo脚手架,就可以得到我们想要的自定义vue目录结构。

发布Generator

Generator实际是一个npm模块,那么发布generator就是发布npm模块,我们需要通过npm publish命令发布成一个公开的模块就可以。

  1. 先创建本地仓库,创建.gitignore文件,把node_modules写入
# 初始化本地仓库
git init
git status
git add .
# 进行第一次提交
git commit -m 'init project'
  1. 打开gitHub创建一个远程仓库
git remote add origin <仓库ssh地址>
# 把本地代码推送到远程master分支
git push -u origin master
# 进行发布
npm publish
# 确定version\username\password
  • 使用淘宝的镜像源是不可以的,因为淘宝镜像源是一个只读镜像,需要先改变npm镜像
  • 推送成功之后再npm官网可以看到,下次就可以直接npm安装了

PS: 如果generator要在官方的仓库列表中出现,需要在项目名称中添加yeoman-的关键词,这个时候Yeoman的官方会发现项目。

举例子,我发了一个demo脚手架去官网,没有什么功能就是练习, generator-csfdemo

查看原文

赞 1 收藏 1 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-25

Windows系统下实现服务器SSH免密登录

目录

  • 原理
  • 实现步骤

    • 一、生成公钥和私钥
    • 二、在客户端编辑配置文件
    • 三、将公钥拷贝到服务器
    • 四、重启ssh服务
    • 五、关闭再打开cmd进行登录

每次登录服务器都要输入密码很麻烦,所以进行免密登录配置之后就可以跳过输入密码的步骤,直接登录服务器.

原理

ssh提供了安全的身份认证的策略,在免密登录之前,首先需要一对公钥和私钥.

客户端拿着私钥,服务端拿着公钥,属于非对称加密.
私钥客户端自己拿着不在网络上传输,公钥可以网络传输存在服务器上,登录的时候拿私钥进行加密,内容发送给服务器,服务器对公钥进行解密,成功解密就告诉客户端建立链接.

实现步骤

一、生成公钥和私钥

用户/.ssh目录,打开cmd,输入ssh-keygen,之后输入文件名称,一路回车就生成了私钥和公钥

二、在客户端编辑配置文件

用户/.ssh目录,打开config文件,如果没有自己新建一个

# 将来可以 ssh aaaa 进行连接,这里可以写IP也可以写域名
Host www.aaa.com

# 可以是域名也可以是IP
HostName  59.110.XX.XXX

# 登录时的用户名
User root
             
PreferredAuthentications publickey

# 本地文件
IdentityFile C:\Users\burning\.ssh\burning
重要的提示!!!
IdentityFile C:\Users\burning\.ssh\burning
用户名的路径上一定不能有中文,如果有就无法实现免密登录.如果你的用户名是中文,要么需要修改成英文,要么就将秘钥放置到别的文件路径中

三、将公钥拷贝到服务器

  1. 先密码登录一次服务器,在服务器目录下执行下面的命令
> pwd 查看当前所在目录
# /root
> cd .ssh
> ls
# authorized_keys 授权者文件,要把公钥放到这里来
> cat authorized_keys # 打印文本文件中的内容,现在是空

用户/.ssh 目录下使用cmd命令将公钥进行拷贝,scp命令后面不能加中文

# 注意下面的root后面跟的IP或者是域名,必须和config中的Host一致
> scp burning.pub root@www.aaa.com:/root/.ssh
# 目录下有了公钥的文件,内容追加到authorized_keys中
> cat >> authorized_keys < burning.pub
> cat authorized_keys 
# 查看内容可以看到公钥已经存进去了

四、重启ssh服务

拷贝完成之后,要重启ssh服务

systemctl restart sshd

五、关闭再打开cmd进行登录

这个时候打开cmd进行登录ssh root@www.aaa.com,可以看到不需要密码,直接登录成功

查看原文

赞 1 收藏 1 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-21

GIT实操手册总目录

目录

  • 整体框架
  • 如果你有啥不懂,这是GIT的维基百科
  • 领导说我Commit太多
  • Git Tag是时间的里程碑
  • 如何回滚不出错
  • 说实话我第一次听到rebase,也是一脸懵
  • 听说摘樱桃是面试必考?
  • GIT使用注意事项

    • GIT提交流程规范
    • 其他GIT注意事项

总共更了有七篇文章是关于GIT实操的,既然是GIT实操不是GIT基础,所以这里并不去介绍GIT的概念和初体验之类的,而是会深入一些比较不常用但是只要能掌握那便可成为很好用的工具的功能。

可能我对于GIT基础的理解,还仅限于git status/git add ./git commit这几个命令吧哈哈。之前让我进行技术分享,我仅对我们部门的人进行过一次GIT的简单不记名调研,关于GIT的小调查,有兴趣了你也可以看看,自己对于GIT的掌握程度,发现近乎50%的人对GIT的掌握只在提交、合并、远程pullpush,所以这个实操就这样诞生了,没错这是一篇我精心准备的技术分享。不过通过这次分享我还是收获了很多很多的知识。

但是GIT的世界远远不止这些,以后我如果钻研到了新的东西也会不间断的总结一篇放进来,学无止境哈哈。废话不多说(已经说了很多了,你知道,天天更干货偶尔也想闲聊两句),上正文吧。

整体框架

一张图就能了解,git究竟可以做什么?

如果你有啥不懂,这是GIT的维基百科

GIT实操手册 —— 值得收藏的GIT常用命令字典

领导说我Commit太多

GIT实操手册——手把手教你如何解决commit太多的情况

Git Tag是时间的里程碑

GIT实操手册 —— Git Tag是时间的里程碑

如何回滚不出错

回滚可不仅限于git reset
GIT实操手册 —— 如何做到精准回滚

说实话我第一次听到rebase,也是一脸懵

GIT实操手册 —— 所以到现在合并分支你只会用 git merge 吗

听说摘樱桃是面试必考?

同事告诉我的,也不知道是不是真的,不过这里要给自己挖个坑,之前研究完,但是却没有留下笔记,所以还要重新看一遍整理。
这里等我整理完再填坑吧~~~~ T T

摘樱桃模式——git cherry-pick

GIT使用注意事项

GIT提交流程规范

GIT实操手册 —— 小白也能轻松掌握的规范化提交代码流程

其他GIT注意事项

GIT实操手册 —— 告诉你不告诉别人的注意事项

查看原文

赞 32 收藏 28 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-20

GIT实操手册 —— 告诉你不告诉别人的注意事项

目录

  • 一、本地修改
  • 二、代码提交
  • 三、代码合并
  • 三、定期维护

没啥,只是一些开发中需要注意注意注意的注意事项。

一、本地修改

  1. 减少无用的commit提交,每次提交的时候,将message写清楚。
  2. 在有意义的commit版本号添加tag标签,便于管理。

二、代码提交

  1. git add之前先用git status看看自己修改了哪些文件,有时候还需要借助git diff确认自己修改的地方
  2. git commit之前用git add查看是否修改的文件都到了暂存区

三、代码合并

  1. 合并总开发分支之前先将总开发分支git pull成最新代码
  2. 合并总开发分支之前先将自己分支所有的commit合并成一个
  3. 合并时候分两步
    3.1 第一步:将总开发分支的代码合并到自己分支并解决冲突,保证解决冲突的地方不是在总开发分支,而是自己的额外分支,避免总开发分支的污染。
    3.2 第二步:到总开发分支合并刚才在自己分支合并过的代码,此时不需要手动处理冲突
  4. git push到远程主机,需要去远程主机上查看是否已经提交上,修改的内容是不是和本地diff的一样

三、定期维护

  1. 对于长久不用的开发分支和远程分支,可以定时进行清理。
  2. 合作开发不可避免会遇到合并别人代码有冲突的时候,这个时候需要的是多进行交流沟通,而不是盲目的选自己的代码留下。
查看原文

赞 0 收藏 0 评论 0

顽皮的雪狐七七 发布了文章 · 2020-12-20

GIT实操手册 —— 小白也能轻松掌握的规范化提交代码流程

目录

  • 背景
  • 流程
  • 步骤

    • 一、 从远程拉取代码
    • 二、 创建并切换到新分支
    • 三、开发之后保存提交代码到本地仓库 or 远程仓库
    • 四、测试完毕合并dev
    • 五、将代码上传到远程仓库

下面只是一些我工作时候的经验总结,也是我进公司的导师这样告诉我的。如果有更好的,欢迎留言。

背景

有时候必要的规范化提交代码的流程,可以帮助我们精准的把控项目的稳定性。这里不强调快是因为,从来规范化的提交代码,从来就是不图快的,线上代码稳定比快更重要。这个是每个公司衡量项目稳定性的尺子,我遇到过,同事上线不按操作来,然后把我的线上代码冲掉的情况,而我具体问他他也说不上来是怎么操作的。

流程

GitLab上面dev是线上分支,开发的时候从远程机器上将最新的dev分支拉取,创建新的分支之后开发,测试完毕之后合并到dev上,解决冲突之后push到远程机器,准备上线。

步骤

一、 从远程拉取代码

git pull origin dev

二、 创建并切换到新分支

git checkout -b newBranch

三、开发之后保存提交代码到本地仓库 or 远程仓库

git status
git add .
git commit -m 'git log'
git push origin newBranch  # 提测之后QA会拉取gitLab上面newBranch分支的代码

四、测试完毕合并dev

  1. newBranch分支中,主要检查代码,为合并做准备
git status # 查看修改的文件目录有没有问题
git diff  # 在提交前先看自己改了什么,没有问题了再进行add
git add .  # 把需要提交的东西放进缓存区
git commit -m '这次提交的题目' #提交代码并填写修改的目录便于以后查找
git rebase -i HEAD~4 #合并4个commit,如果只有一个commit的话就不需要合并直接git rebase
git checkout dev  #切换到dev分支
  1. dev分支中,将dev代码更新成最新的
git pull origin dev #远程存储库中的代码进行提取并合并到dev分支中,保证dev中的代码是最新的
git checkout newBranch  #切换到newBranch分支
  1. newBranch分支中,开始合并
git rebase dev #合并dev分支到newBranch中
#'如果有冲突的话'
git status  #看一下是哪个文件冲突
git diff 'file'  #看一下file文件的那些部分需要解除冲突,并手动解除冲突
git add . #把解除完冲突的文件提交到暂存区里
git rebase --continue  #继续rebase
#'如果想退回rebase之前的状态'
git rebase --abort
#'成功之后'
git checkout dev #切换到dev分支
  1. dev分支中,合并newBranch分支的代码
git merge newBranch   #合并newBranch分支

五、将代码上传到远程仓库

git push origin dev #上传代码到远程仓库,更新最新的代码
查看原文

赞 0 收藏 0 评论 0

认证与成就

  • 获得 188 次点赞
  • 获得 2 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 2 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-06-26
个人主页被 3.4k 人浏览