Code Monkey home page Code Monkey logo

combing-notes's Introduction

Hi there 👋

⌨️ I'm using the following extensively:

JavaScript Vue.js SASS TypeScript Markdown Webpack

combing-notes's People

Contributors

toxicfy avatar

Stargazers

 avatar

Watchers

 avatar

combing-notes's Issues

JS常用的数据处理方法

目前来说处理数据的方法会很多,包括ES6中有迭代器的概念用于统一接口处理处理不同结构;所以这里不会依次罗列,讲的一些是比较常用的,当然语意明确,书写简洁同样是选择方法的指标之一

首先了解几个基础场景:

  • 添加、删除数据

初始的方法中例如pushunshift等方法是修改原数组返回的是数组的长度;现在解构赋值可以帮我们完成数据的浅拷贝并返回新的数据,要求不改变原数组的推荐使用:

const original = ['node','javascript','http'];
const originalUnshift = [...original, 'vue'];  // unshift
const originalPush = ['react',...original];	  // push
const originalConcat = original.concat(['typeScript','deno'])

//👍👍推荐:可以在任意位置添加及删除元素(startNum,delNum,addItem)
let list = ['Jan', 'March', 'April', 'June'];
list.splice(1,1,'newItem');  //list: ['Jan', 'newItem', 'April', 'June']
  • 判断是否存在某个值
//Array.prototype.includes(searchElement, fromIndex);
let nums = [1, 2, 3], 
    pets = ['cat', 'dog', 'bat'];

nums.includes(1,2); //false
pets.inclides('cat'); //true

//原来多采用 indexOf() === -1 进行判断
  • 获取数组中某一元素的下标
const posts = [
  {id: 13, title: 'Title 221'},
  {id: 5, title: 'Title 102'},
  {id: 131, title: 'Title 18'},
  {id: 55, title: 'Title 234'}
];
// 找到id为131的元素
const requiredIndex = posts.findIndex(obj => obj.id === 131);
console.log(requiredIndex); //2

接下来着重了解数据展开部分:

filter、find、map、reduce、every

🔥🔥🔥filter: 筛选数组或类数组,过滤掉不符合筛选条件的元素并返回符合条件的数组

// Array.filter(callback(element,index,prevArr),[,第二个参数是callback绑定的this])

let prevArr, filterArr;
prevArr = [1, 2, 3, 4, 5, 6]
filterArr = prevArr.filter(ele => ele % 3 === 0);  //[3,6]

🔥🔥find: 返回数组或类数组中符合条件的第一个元素(参数同上)

let prevObjectList, filterObjectName;
prevObjectList = [
  {name: 'tom', age:19},
  {name: 'jack', age:12},
  {name: 'lucy', age:21},
]
filterObjectName = prevObjectList.find(ele => ele.age < 18).name; //jack

🔥🔥🔥 🔥 map:每个元素都会触发这个函数并返回对应的结果,类似所有元素的一个转换器

let arr, doubleArr;
arr = [1, 2, 3, 4, 6, 7];
doubleArr = arr.map(i => i*2); // doubleArr [2, 4, 6, 8, 12, 14],而不会对Arr造成以影响;

示例:

实现数组的小写且不影响到原来数组:

//这样的代码我们的项目里面也是最多的😐😐😐
let validData, lowercaseEmails,
	mixedEmails = ['[email protected]', '[email protected]', '[email protected]'];

function getEmailsInLowercase(emails) {
  lowercaseEmails = [];
  for (let i = 0; i < emails.length; i++) {
    lowercaseEmails.push(emails[i].toLowerCase());
  }
  return lowercaseEmails;
}
validData = getEmailsInLowercase(mixedEmails);

更清晰的一些:

//我们的代码中也有,替换循环(主要为使用forEach和map)😅😅😅
let validData, lowercaseEmails,
	mixedEmails = ['[email protected]', '[email protected]', '[email protected]'];

function getEmailsInLowercase(emails) {
  lowercaseEmails = [];
  //简洁性更强,同时避免了添加索引记录访问位置
  emails.map( email => {
    lowercaseEmails.push(email.toLowerCase());
  });
  return lowercaseEmails;
}
validData = getEmailsInLowercase(mixedEmails);

但实际上这依旧是不ok的,主要是命令式编程涉及的细节太多了;细节如下:我需要取出列表中第一个元素,然后把它转换成小写,再存储到另一个列表中,最后返回这个列表;

实际上我们并不关心这个过程,而应该是:“我有混合大小写的邮件列表,我需要一个全是小写的邮件列表,这要写一个能够进行小写转换的函数“,这样可读性才会提高;

let mixedEmails = ['[email protected]', '[email protected]', '[email protected]'], validData;

function downcase(str) {
  return str.toLowerCase();
}
validData = mixedEmails.map(downcase);

🔥🔥🔥 🔥 reduce: 将N个数据进行处理后返回一个实例:这个用的也超多

//Array.prototype.reduce(callback [, initialValue (初始值)])
const posts = [
  {id: 1, upVotes: 2},
  {id: 2, upVotes: 89},
  {id: 3, upVotes: 1}
];

const totalUpvotes = posts.reduce((totalUpvotes, currentPost) =>     
  totalUpvotes + currentPost.upVotes, //reducer 回调函数
  0 // 初始化投票数为0
);
console.log(totalUpvotes)//输出投票总数:92

//其中callback()中第一个参数是上一次调用回调时返回的值,第二个值是数组中正在处理的元素;

🔥🔥 every: 有些判断场合用起来不错

//每个元素都满足条件才会返回true:
let judge = [1, 2, 3, 4, 5].every( val => val > 1);  // false

前端代码规范

目前已经在公司正式工作一个月了,但是在开发过程中没有严格的代码的规范,所以在这里主要是结合这个月中写代码的一些不好的情况做了一个规范的整理,希望可以督促自己改良代码风格

javascript部分

全局变量

全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,他们住在同一个全局命名空间,所以当程序的两个不同部分定义同名但不同作用的全局变量的时候,命名冲突在所难免。

避免创建隐式全局变量

可移植性同样是避免全局变量的原因:比如把一个方法移植到另一个命名空间里面(或者是另一个文件中)来完成功能整合的时候, 可能我们关注的只是方法名没有重复,但是内部创建的隐式全局变量可能会造成很大的影响

  • 未声明直接使用
function sum(x, y) { 
   // 不推荐写法: 隐式全局变量
   result = x + y; 
   // let result = x + y;
   return result;
}

此段代码中的result变量没有声明。代码照样运作正常,但在调用函数后你最后的结果就多一个全局命名空间,这可能是一个问题的根源

  • 链式分配创建
var a = b = 0;
// => 从右到左的赋值的规则 var a = (b = 0);
b = 0; 
var a = b;

提议:

  • 在函数顶部使用单个letval语句进行的批量声明变量
function myFunc() {//function body
    var a = null, 
        b = Math.random(), 
        c = "value";
        //优点:单一的地方去寻找功能所需要的所有局部;
	   // 避免在定义之前使用的逻辑错误(提升可以访问,但是赋值为undefined)
}
  • 当你循环获取值时,缓存数组(或集合)的长度是比较好的形式
for (let i = 0, max = myArray.length; i < max; i++) {   
	// 使用myArray[i] 
}

注释

js是非常动态的脚本语言,当缺乏注释,在重构代码(或者说阅读其他人代码)时,经常遇到的一个问题是在运行到这里时,这个应该是什么类型,这种状态下取什么值?本质上来说还是程序往往是围绕数据编程

所以通常应该记录是函数(它们的功能简述参数以及返回值)或是其中特殊的;仅需要注释和函数属性名来理解代码内容;

最好在时间充裕且重要部分写行注释,

Alt text|center|

最重要的习惯,然而也是最难遵守的,就是保持注释的及时更新,因为过时的注释比没有注释更加的误导人。

命名

函数

命名方式:使用普通函数使用小驼峰式的命名,造函数(class)使用大驼峰式命名;
建议规则:前缀为动词,

  • can(返回一个布尔值判断是否可以进行)
  • has/is(返回一个布尔值判断是否存在/为某个值)
  • get/set(返回目标对象)
//是否可以删除
function canDelete(){
    return true;
}

//获取姓名
function getName{
    return this.name
}

//创建类
class PersonHabitAll{
    constructor(sleepTime,diet){
        this.sleepTime = sleepTime;
        this.diet = diet;
    }
}

变量

  • 变量的命名不以短小为有点,应该用相当规范的单词准确传达意思, 当然建议也不要大于三个单词的例如:maxNumberOfTeamMembers这样也不易读取

不好的变量名
id
num1/num2

好的变量名
userId / fileId
maxNum / minNum

  • 对于相对的功能尽量使用对仗工整的单词
    • up/downbegin/endopened/closedvisible/invisiblescource/target,对仗明确可以让人很清楚地知道两个变量的意义和用途

常量

命名方法 : 全部大写
命名规范 : 使用大写字母和下划线来组合命名,下划线用以分割单词

 const COUNT = 100;
 const REQUEST_URL = '192.168.10.9';

ps:这个还是很有必要的,我们常见的情况是给一个函数传递不同的的时候传递一个数字作为不同的分类,这个时候往往会出现必须写较多的注释才能缕清结构,所以建议创建成常量,这个在build的很多文件中都有体现;

//var function
//let const
//这样的代码实际上会让人感觉很困惑,这些都是什么
someThingHandler.fnTrigger("handlerName", 3, true);

//所以可以使用常量的方式进行书写
const ACTIVE_STATUS = {
    ACTIVE_DEFAULT: 0,
    DOT_ACTIVE: 1,
    LINE_ACTIVE: 2,
    FACE_ACTIVE: 3,
    //...
}
someThingHandler.fnTrigger("handlerName", ACTIVE_STATUS.FACE_ACTIVE, true);

判断

假值的判断

在我们使用判断某个状态的时候,不仅仅是通过类似canDoSomething的函数直接拿到返回值的情况,往往也会遇到判断某个数据类型进行作为判断语句:下面就是所有在javascript中判断为否的情况:

false/null/undefined/0/NaN/'' (空字符串)

三元条件判断

if (x === ACTIVE_STATUS.ACTIVE_DEFAULT) {
    return false;
} else {
    return true;
}
//推荐使用
return x === ACTIVE_STATUS.ACTIVE_DEFAULT ? false : true

隐式转换

这个本身没有什么问题,但是因为目前来说我们对隐式转换并不是所有的都能掌握,所以还是尽量在做判断条件的时候进行避免这种情况,使用=== 也可以直接了解到该值的数据类型

null == undefined          //true
'' == '0'                  //false
0  == ''                   //true
0  == '0'                  //true
new String("abc") == "abc" //true
new Boolean(true) == true  //true
true == 1                  //true

在自己对转换有有一定的掌控力的时候吧,就可以自己使用进行转换( ps:如果有类型转换自己做类型转换,不要让别人去猜这里面可能类型转换),可以更加方便

//字符串转换为整型
let value = "11";
let newPrice = +value; 
value === 11	//true

//判断其他类型为boolean,使用 !!
if(!!someTypeValue) {
	//dosomething
}

拷贝

在代码的编写中会频繁的遇到拷贝的情况:由于基本数据类型值不可变,每次赋值都是创建一个新的数据,所以对于基本类型就不需要注意太多(当然需要注意比较是值的比较,只要它们的值相等就认为它们就是相等);

这里主要提一下引用类型

  • 它们的值是可变的
var a = [666,999];
a[1] = 2333;
console.log(a[1]); // 2333
  • 引用类型的比较实际上是引用的比较,本身引用对象是存在堆内存(和栈内存主要的区别在于分配和释放,前者大小不定不会进行自动释放,后者自动分配大小且由系统自动释放)里面的,我们的名实际也就是指向内存的指针;
var a = [666,999];
var b = [666,999];
a === b //false

继续看的话:

var a = {}; // a保存了一个空对象的实例
var b = a;  // a和b都指向了这个空对象

a.name = 'tom';
console.log(a.name); // 'tom'
console.log(b.name); // 'tom'
console.log(a == b);// true

实际上这个只是把a的引用复制了一份并传递给b,使得b同样指向了a所指向的那个引用类型(对象),这实际也不是拷贝,只是传递地址;

在js中的拷贝改变新数据都不会使得原数据一同改变,只是在于深浅,也就是常说的深拷贝和浅拷贝;

它们的区别也就在于属性的复制层数
浅拷贝的话会将各个属性进行依次复制,并不会进行递归复制,也就是说只会赋值目标对象的第一层属性;
深拷贝的话不只拷贝目标对象的第一层属性,而是递归拷贝目标对象的所有属性;
这里贴出jq-extend的源码和注释

jQuery.extend = jQuery.fn.extend = function() {
  var options,
    name,
    src,
    copy,
    copyIsArray,
    clone,
    target = arguments[0] || {},
    i = 1,
    length = arguments.length,
    deep = false;

  // 如果第一个参数是布尔值,则为判断是否深拷贝的标志
  if (typeof target === "boolean") {
    deep = target;
    // 跳过 deep 标志,留意上面 i 的初始值为 1 ,重新给目标进行赋值
    target = arguments[i] || {};
    // i 自增 1,== 2
    i++;
  }

  // 判断 target 是否为 object / array / function 以外的类型
  if (typeof target !== "object" && !isFunction(target)) {
    target = {}; // 如果是其它类型,则强制重新赋值为新的空对象
  }

  // 如果只传入1个参数;或者是传入2个参数,第一个参数为 deep ,第二个为 target
  // 所以 length 的值可能为 1 或 2,但无论是 1 或 2,下段 for 循环只会运行一次
  if (i === length) {
    // 将 jQuery 本身赋值给 target
    target = this;
    // i 自减 1,可能的值为 0 或 1
    i--;
  }

  for (; i < length; i++) {
    // 以下拷贝操作,只针对非 null 或 undefined 的 arguments[i] 进行
    if ((options = arguments[i]) != null) {
      // Extend the base object
      for (name in options) {
        src = target[name];
        copy = options[name];
        // 避免死循环的情况
        if (target === copy) {
          continue;
        }
        // Recurse if we're merging plain objects or arrays
        // 如果是深拷贝,且copy值有效,且copy值为纯object或纯array
        if (
          deep &&
          copy &&
          (jQuery.isPlainObject(copy) || (copyIsArray = Array.isArray(copy)))
        ) {
          if (copyIsArray) {
            // 数组情况
            copyIsArray = false;
            clone = src && Array.isArray(src) ? src : [];
          } else {
            // 对象情况
            clone = src && jQuery.isPlainObject(src) ? src : {};
          }
          // 克隆copy对象到原对象并赋值回原属性,而不是重新赋值
          // 递归调用
          target[name] = jQuery.extend(deep, clone, copy);
        } else if (copy !== undefined) {
          target[name] = copy;
        }
      }
    }
  }
  //返回修改后的目标
  return target;
};

当然我们想要实现拷贝的话有几种方案

  • 浅拷贝
//object
let copy = Object.assign({}, obj);

//Array.prototype.concat()/Array.prototype.slice()
let newArr = oldArr.concat()
let newArr2 = oldArr2.slice()

//使用函数
function shallowClone (source){
	if(!source || typeof source != 'object'){
	  return;
	}
	var targetObj = source.constructor === Array ? [] : {};
	for(var keys in source) {
	  if(source.hasOwnProperty(keys)){//此处避免遍历到原型上的方法
	      targetObj[keys] = source[keys];
	  }
	}
	return targetObj;
}
  • 深拷贝:
//JOSN对象中的stringify可以把一个js对象序列化为一个JSON字符串,parse可以把JSON字符串反序列化为一个js对象,通过这两个方法,也可以实现对象的深复制

var dest = JSON.parse(JSON.stringify(target));

//jquery
let obj2 = $.extend(true, {}, obj1);

//lodash
var lodash = require('lodash');
var oldObj = {
    a: [1],
    b: { f: { g: 1 } },
};
var newObj = lodash.cloneDeep(oldObj);

说了这么多关于拷贝,实际是在我们进行引用类型赋值的时候要知道一个=仅仅是指针的复制,和原对象依旧共用一个内存地址,修改新的内容也会修改原对象,这点需要注意,如果想要不互相影响,就使用拷贝的方式。

CSS部分

仅和样式相关的避免使用驼峰命名和 _ 的方式命名,而是用 -进行单词的连接;

ID和class的名称总是使用可以反应元素 目的和用途 的名称

//应当避免名字去反映自己写的css规则; 不要使用样式特点命名,而是突出用途
.fw-1000 {
  font-weight: 1000;
}
.red {
   color:red;
}

//  =====>

.font-heavy {
   font-weight: 1000;
}
.danger {
  color:red;
}

一般情况下ID不应该被用于样式,并且ID的权重很高,所以不使用ID解决样式的问题,而是使用class

0后面不带单位

//padding-bottom: 0px;
//=>
padding-bottom: 0;

尽量使用缩写属性对于代码效率和可读性是很有用的,比如font,margin,padding等

.padding-item{
  padding-bottom: 2em;
  padding-left: 1em;
  padding-right: 1em;
  padding-top: 0;
}
<!-- 修改为 -->
.padding-item{
  padding: 0 1em 2em;
}

more

尽量注意的,最好在编写完成后按照结构属性和表现型属性前后进行整理;

Alt text
可以使用css(2/3)的选择器更好的精简代码,提高可读性

  • 子选择符
    label1 > lalel2 //label1必须是label2的父元素,不能是其他的上级元素。

  • 一般同胞选择符
    label1 ~ label2 //label2必须跟在同胞标签label1的后面,不一定紧跟。

  • 紧邻同胞选择符
    label1 + label2 //label2必须紧跟label1的后面。

  • 属性值选择符:标签名[属性名=属性值]。与属性名选择符基本类似,只是为属性名添加了特定的值限制

<img src="" title="border" alt="flower"/>

//css
img[title="border"] {
	display:none;  //title="border"的img元素会使用该样式
}
  • 伪类
    :hover/:first-child:last-child,:nth-child(n),:checked等,这里不做展开,只是提及
<ul class="list">
    <li>tom</li>    //选中,蓝色
    <li>jack</li>
    <li>tony</li>
</ul>
//css
.list li:first-child {color:blue;}

html部分

需要注意就是结构样式分离,结构合理清晰;

有必要可以使用语义化标签会更好;

imgalt属性尽量加上

参考文档:

Redux (一):action、store、reducer分析

简述及特性

reduxJavaScript 状态容器,提供可预测化的状态管理 ; 实际上是提供了一个在状态读写很复杂的情况下完成在同一个地方进行状态的查询改变以及传递;

npm install --save react-redux  //react中进行使用

先放图 :

  • state 是只读的 , 只读是不允许对state 进行直接的赋值和修改,在redux 中是通过 action 和 reducer 返回一个新的 state 进行改变;
  • 单一数据源 : 仅存在一个Store作数据的管理;

redux-process

重要的概念及流程

Action

Action 本质上是 一个规定了typedata的对象; 其中的datastore数据的唯一来源;

//actionType.js
export const ADD_VALUE = 'add_value';

//createAction.js
import { ADD_VALUE } from './actionType.js'
export const crateAction = data => {
   type: ADD_VALUE, //用于标识不同的action,通常定义成字符串常量
   data // 来源--用及其他组件状态传递
}

action描述了发生了什么, 通常情况下我们会在store中对action进行生成然后通过dispatch(action)的方式将action传递到Reducer中进行处理;

Reducer

指定了应用状态的变化即 : 如何响应 actions的行为并将数据进行更改后发送到 store ;

值得注意的是: reducer 是纯函数 ,只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

(prevState, action) => newState;	//接收原来的state以及action,返回新的state
import { ADD_VALUE } from './actionType.js';

//处理传递的Action,是一个纯函数
export default (state = "", action) => {
  const newState = JSON.parse(JSON.stringify(state));
  switch (action.type) {
    case ADD_VALUE:
      newState.date = action.data;
      return newState;
    default:
      return state;
  }
};

Store

reduxstore是唯一的;

创建:

从 redux 包中导入 createStore 这个方法,并使用 reducer 纯函数作为一个参数;

import { createStore } from "redux";
import reducer from "./reducer";
const store = createStore(reducer);// 创建 store
export default store;

使用:

Store依托于components来聊的话,就是作为action的传递者, 在组件中常用api完成action的传递

// component
import React, { Component } from "react";
import { crateAction } from "./crateAction/js";
import store from "./store";
class Test extends Component {
  render() {
    this.handleStoreChange = this.handleStoreChange.bind(this);
    this.emitMethods = this.emitMethods.bind(this);
    this.state = store.getState(); //初始化state
    store.subscribe(this.handleStoreChange); //监听store数据变化
  }
  handleStoreChange() {
    this.setState(store.getState()); //更新组件的store,view进行重新渲染;
  }
  emitMethods() {
    const action = crateAction(data);
    store.dispatch(action); //传递action到Reducers
  }
}

Vue-Router(三):路由导航守卫及 keep-alive

导航守卫及 keep-alive 作用下的生命周期

lifeCycle

keep-alive

在进行 Vue 项目开发的时候,很多组件是没必要多次渲染以及请求数据;为了避免多次刷新请求,Vue 提供了一个内置的抽象组件 keep-alive不渲染 DOM && 不在父组件链中)来缓存组件内部状态;

用法:

包裹动态组件时,缓存不活动的组件实例

<keep-alive> <component v-if="showComponent" /> </keep-alive>

缓存部分组件或者组件: 大部分的使用场景,与 router-view 结合使用

  • router-view : 配置路由对象的 meta 字段进行判断缓存部分页面或者组件
<template>
  <keep-alive>
    <router-view v-if="$router.meta.keep_alive"></router-view>
  </keep-alive>
  <router-view v-if="!$router.meta.keep_alive"></router-view>
</template>
// router
import Index from './component/index'
const router = [{
    path:'/',
    redirect:'/index',
    component: Index
    meta:{
        keep_alive: true // 该组件需要被缓存
    }
}]
  • 在新的版本(2.2.0 后)中,我们可以使用 include (需要被缓存)/ exclude(不需要被缓存) 进行缓存的设置, 同时我们需要提前设置组件的 name 属性,同时可以使用 bind动态设置(支持正则
<!-- 在使用过程中:exclude的优先级大于include -->
<template>
  <keep-alive include="a,b"></keep-alive>
  <keep-alive v-bind:include="includedComponents"></keep-alive>
</template>

钩子函数

通过该组件包裹的动态组件/路由,会附加两个生命周期函数 activateddeactivated , 用于精确的控制行为;在 2.2.0 及其更高版本中,activated 和 deactivated 将会在树内的所有嵌套组件中触发;

此处简单回顾一下组件的生命周期函数

我们一般将数据请求放置在 created 钩子函数中,因为在 beforeCreated 后就开始监测数据变化以及初始化内部事件了; 当然由于没有挂载数据,所以涉及到 dom 放置在 mounted 中进行, 当模板编译完成后进行数据的挂载 mounted 以及更新 updated,之后离开组件销毁组件;

那么在使用了 keep-alive 后我们的周期如何呢?

  • 如果不缓存或者第一次进入缓存组件下:钩子的触发顺序为使用 created -> mounted -> activated 进行组件的数据请求以及数据的挂载, 将触发请求写在 created 生命周期中,就可以实现数据缓存;
  • 路由进入缓存完成的组件后,组件不会触发 beforeCreate/created/beforeMount/mounted, 当我们需要获取到新的数据的时候,也就是在 activated 阶段进行数据的更新;

导航守卫

在很多时候我们需要通过路由进行验证操作(例如:登录的权限验证)、路由跳转前后组件状态控制(例如:当该组件状态未保存的情况下取消跳转等)以及缓存组件的 数据加载(也可以在 activated 中完成)等;

所以我们需要在路由的跳转过程中更多的控制点,于是 Vue Router 提供了导航守卫(全局的,路由独享以及组件内守卫)植入到整个过程中进而完成更多的控制需求。

全局守卫:

实际上是 router 实例对象的方法

  • router.beforeEach(callback) 全局前置守卫 进入路由之前
  • router.beforeResolve(callback) 全局解析守卫 (特别的: 在进入的组件 beforeRouterUpdate 钩子后调用;
  • router.afterEach(callback) 全局后置钩子 进入路由之后
import router from "./router";

/**
 * 通过全局守卫简单验证:
 * @param {object} to  - 将要进入的路由对象 (组件中的this.$route)
 * @param {object} from - 将要离开的路由对象
 * @param {function} next - 跳转新路由,参数可以是path,路由对象,false等,跳转结果与参数相关
 */
router.beforeEach((to, from, next) => {
  const role = localStorage.getItem("user_name");
  if (!role && to.path !== "./login") {
    next("./login");
  } else if (to.meta.permission) {
    //meta: { title: '管理员权限页面', permission: true }
    role === "admin" ? next() : next("/403");
  } else {
    next();
  }
});

路由组件内守卫:

  • beforeRouteEnter(callback) 进入路由前(无法访问到this
  • beforeRouteUpdate(callback) 路由复用同一个组件时(例如通过参数查询进行的路由跳转, /user/tom 跳转到 /user/admin)
  • beforeRouteLeave(callback) 离开组件对应路由时调用(禁止用户离开,setInterval 销毁等)
//参数的意义和全局守卫相同
beforeRouteEnter((to, from, next) => {
  next(callback); //此处的callback会在进入的组件mounted周期之后进行调用,所以可以进行数据的请求用于更新dom
});

Redux (二):异步流程及中间件(redux-thunk、redux-saga)

异步操作

一般情况下在action(此时为一个包含type数据的对象)被创建完成后,被store.dispath(aciton)触发后会直接将action传递给reducer进行处理,这就是一个同步操作完成数据传递的流程 ;

redux-middleWare

当我们进行异步操作,想要完成一次数据的重新构造生成就必须处理数据之前需要获得保证原始数据~即获得: reducer(prevValue,action)中的action.data部分;

所以我们需要使用中间件(Middleware)来改写store.dispath()方法完成数据的传递;

中间件的用法

redux-thunk

函数类型的action可以被middleware捕获,异步逻辑在action创建,在component的生命周期函数中进行调用进而达到简化逻辑以及异步数据传递;

下面是使用redux-thunk的流程:

创建 store

import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import reducer from "./reducer";
// 使用 redux-devtools以及中间件redux-thunk
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
  ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
  : compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const store = createStore(reducer, enhancer);// 创建 store
export default store;

创建 action

const initListAction = data => {
  type: "",
  data
};

export const getTodoList = () => { // 返回一个函数
  return (dispath) => {
    axios.get("./api/v1").then(res => {
      const action = initListAction(res.data);// 创建action
      dispath(action);// 触发action
    });
  };
};

使用 Component

 // 异步请求
  componentDidMount() {
    const action = getTodoList();
    store.dispatch(action);
  }

redux-saga

相比较redux-thunkredux-saga同样可以完成对store.dispath()的改造,并且将异步请求的代码封装到新的文件中进行管理

安装和初始化

在生成store中生成并使用saga中间件,并且引入独立的sagas.js文件;

//安装redux-saga
npm install --save redux-saga   

//生成store.js
import { createStore, applyMiddleware } from 'redux'
import createSagaMiddleware from 'redux-saga'
import reducer from './reducers'
import mySaga from './sagas'

const sagaMiddleware = createSagaMiddleware() //生成中间件
const store = createStore(
  reducer, applyMiddleware(sagaMiddleware)
)

sagaMiddleware.run(mySaga) //运行saga

编写sagas中的内容及触发

创建action不必指定对应的state,在view中的component中使用直接生成action并触发后, 中间件捕捉到对应打action后在saga.js中处理

//createAction.js
export const initAction = () =>({
   type: "GET_LIST"
});

//component
componentDidMount(){
    const action = initAction();
    store.dispatch(action)
}

//saga.js
import { put, takeEvery } from 'redux-saga/effects'
import initAction from './createAction';
import axios from 'axios'

// 将在 GET_LIST action 被 dispatch 时调用
function* getList(action) {
    try{
      const res = yield axios.get('./api/v1');
   	  const action = initAction(res.data);
   		yield put(action);
    }
    catch(e){}
}

function* mySaga() {
  yield takeEvery("GET_LIST", getList);
}
export default mySaga;

对于使用而言的

redux-saga虽然将action异步的部分进一步拆分,并且使用Generator函数完成异步更新reducer中传递过去的action的值,但是显得十分繁琐;

redux-thunk实际上将 action 由普通的对象修改 为函数,并在函数完成执行后进行传递到reducer中;

Redux (三): 使用React-Redux库绑定React

这是Redux 官方提供的 React 绑定库 , 通过 react-redux 的干预下获得ReduxStore中的值 , 并通过connect函数完成数据的getdispatch(action);

概述

在项目中进行安装

npm install --save react-redux

相比较未使用的redux写法, 发生改变的实际上在于

  • 组件如何获取到Store中数据;
  • 组件中如何对Store进行变化的监控;
  • 组件中如何触发对应处理数据的reducer;

API 说明

组件如何获取到Store中数据

<Provider store> 使组件层级中的 connect() 方法都能够获得 Redux store

//正常情况下,根组件应该嵌套在<Provider>中才能使用connect()方法。
ReactDOM.render(
  <Provider store ={store}>
    <MyRootComponent />
  </Provider>,
  document.getElementById('root')
)

组件如何完成Store的映射以及传递actionreducer

import { connect } from 'react-redux' 
//通过react-redux中的connect方法完成映射以及事件触发
export default connect(mapStateToProps, mapDispatchToProps)(showComponent);

这里的 mapStateToPropsmapDispatchToPrps 是两个函数, 分别用于将 store 中的数据映射到组件和完成行为action传递到 reducer的作用 ;

react绑定使用

在入口文件中包裹根节点

import react, { createStore } from 'react';
import { Provider } from 'react-redux';
import App from './component/app';
import reducer from './reducer'
const store = createStore(reducer);

render(
  <Provider store: { store }>
  	<App />
  </Provider>,
  document.getElementById('root');
)

reduceraction生成和原来没有区别,简单的罗列一下;

//action
export const actionName = id => ({
  type: 'ACTION_NAME',
  id
})


// reducer
const todos = (state = [], action) => {
  switch (action.type) {
    case 'CASE_NAME':
      //do something
      return newState
    default:
      return state
  }
}

通过connect将展示组件转换为容器组件, 完成数据的传递;

import react from 'react';
import { actionNameOne } from './action';
import { connect } from 'react-redux';

const showComponent = (props) => {
  const { inputValue, list, handleBtnClick, handleInputChange } = props;
  return (
    <div>
      <div>
        <input value={inputValue} onChange={handleInputChange}/>   
      </div>
      <button onClick={handleBtnClick}></button>
    </div>
  )
}
//state就是传递出来的Store.getState()中的数据
const mapStateToProps = (state) => {
  return {
    //关联组件中this.props.value
    inputValue: state.inputValue,
    list: state.list
  }
}

//dispacth完成Store.dispatch()方法
const mapDispatchToProps = (dispatch) => {
  return {
    //关联组件中的绑定的方法
    handleInputChange: (e) => {
      const action = actionNameOne(e);//创建action
      dispatch(action);//触发action
    }
  }
}
//返回一个容器组件,组件可以不用手动订阅和触发监听完成数据的更新;
export default connect(mapStateToProps , mapDispatchToProps)(showComponent);

Vue-Router(二):路由实例构建选项及传值

构建Router选项

实际上在使用 Vue Router 构建 router实例的时候,参数不仅仅是一个 routes 配置参数,主要是在全局设定路由模式自定义激活样式以及定义滚动行为,这里翻阅了官网对应部分的 Api 并展示如下:

const router = new VueRouter({
  routes,
  base: "/app/", // 默认为'/' ; base的路径加在所有的路径前面 ex:/user:id => /app/user:id

  // 关于路由模式
  mode: "hash", // 浏览器端:hash(defalut)/history ; Node.js: abstract
  fallback: true, // 当mode:history且浏览器不支持 history.pushState 控制路由是否应该回退到 hash 模式

  // 可以用于进行对<router-link>组件进行设置自定义样式;
  // 可以设置激活的class类名,以及包含在父级下的精确激活类名: 下面展示的是默认值
  linkActiveClass: "router-link-active", 
  linkExactActiveClass: "router-link-exact-active",

  // 当路由进行跳转时候如果保存过滚动信息的话,可以在完成路由跳转时候完成页面的滚动
  scrollBehavior: (to, from, savedPosition) => {
    if (savedPosition) {
      return savedPosition;
    } else {
      return { x: 0, y: 0 };
    }
  }

  // parseQuery / stringifyQuery  -- 提供自定义查询字符串的解析/反解析函数
});

通过路由传递参数

router = [
  {
    path: "/user/:id",
    component: User,
    props: true,
    name: "user", // name 可以使得在使用router-link 、 router.push 传入对象 {name: user} 直接访问
    meta: {
      // 元信息,在访问路由对象的时候可以进行访问,可以配合路由导航守卫
      title: "User component",
      description: "this is user component"
    }
  }
];

通过 params 的方式传递参数,这样我们拿到 this.$route 也就是当前的路由对象,所以很容易完成参数的传递; 但是我们这样获取到的动态参数其对应路由形成高度耦合,这样当前的组件便不能进行复用;

所以我们会设置 props 的方式解耦动态参数的获取,不用从路由对象获取;

此时 route.params 或者 会作为组件的属性直接被访问,这样也完成了组件和 Url 的解耦; 对于设置 props 的值的不同,在组件中的使用也略有不同:

//  path: "/user/:id", props: true,  ---- 布尔值

const User = {
  props: ["id"],
  template: "<div>User {{ id }}</div>"
};

// { path:'/login', component:Login, props: { userName: 'Tom'} }  --- 对象
const Login = {
  props: ["userName"],
  created() {
    console.log(this.userName);
  }
};

// 当返回函数的时候可以接收一个参数,就是包含路由信息的route对象,同样的我们在组件中进行访问属性。
const propFun = route => ({
  userName: "Tom",
  age: route.parame.age
});

router = {
  path: "/login",
  component: Login,
  props: propFun
};

// /login/?age=20  =>  {userName: "Tom", age:20}

Vue-Router(一):项目构建及基础功能

综述

在使用Vue做构建单页面应用的时候,Vue Router充当的角色是路径管理器,通过组合组件,并将组件映射到路由,确定组件的渲染位置,进而完成路径切换,完成组件的切换或更新;

开始使用

下载 -- 导入 -- 配置

下载依赖 后在 main.js 中引入使用 ,创建路由对象并配置路由规则,然后将路由对象传递给Vue实例加入在options中加入 router ,并在模板中为router留出渲染位置;

下载 && 基本使用

完成上面的操作后,我们就成功的在一个vue项目中引入并使用了含有路由信息的Vue Router,进而在 url发生变化的时候完成渲染区域的;

编程式导航

提及在上面的一些内容里面,我们使用的是在 HTML 中进行使用 router-linkto 属性完成跳转,这样的标签会被渲染成 a 标签,实际上我们将Vue Router 加入到 Vue 后我们可以访问到路由实例,并向实例进项操作,完成编程式的路由跳转,; 下面类比routerroute

// this.$router && this.$route( // 一个路由实例可以包含多个路由对象,为父子包含关系)
export default {
  computed: {
    userName() {
      return this.$route.params.userName; //当前路由实例跳转的路由对象
    }
  },
  methods: {
    goBack() {
      //整个的路由实例,利用push方法传入一个路由对象
      window.history.length > 1 ? this.$router.go(-1) : this.$router.push("/"); 
    }
  }
};

路由的动态匹配 && 嵌套

实际上在项目中,一个组件并不是只能对应固定数据的渲染,在不同的参数下请求不同的数据并进行数据请求,完成数据的展示;这个时候我们会考虑使用动态路由,完成数据匹配

动态路由

当组件内部需要进行一些内容切换展示的时候,我们考虑在将路由配置中添加child进行嵌套,这样用来完成相对复杂的结构 。下面是嵌套路由结合动态匹配的代码片段:

嵌套路由

命名路由 && 命名视图 && 重定向 && 别名

routes 配置中给某个路由设置名称,可以在路由层级嵌套较深的时候进行方便的链接;

这里提及到利用vue组件的异步加载和webpack机制完成一个路由的懒加载,从而降低首屏加载压力;

命名路由 && 编程式导航

当结构继续复杂化,考虑在组件中加入新的渲染区域 router-view , 然后使用视图的命名指定组件渲染区域,默认为defalut,加上配置的别名重定向如下:

命名视图

最后:

在默认的情况下,Vue Router 使用hash(#)的方式指导浏览器在hash后发生改变的时候进行根据映射关系渲染指定位置 (router-view) 下的不同数据,而不会重新加载网页;当通过参数匹配是并且不会触发组件的生命周期,这就涉及到对hash发生变化后的处理;

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.