My Little World

learn and share


  • 首页

  • 分类

  • 标签

  • 归档

  • 关于
My Little World

布局

发表于 2018-12-04

实现布局的几种方式

1.从0开始用 CSS实现
2.使用 CSS Grid 系统 (通过class实现,无需关心布局如何实现,同时可以适应不同屏幕的尺寸)
3.使用组件库,例如 antd(通过组件标签实现)

布局常见场景:侧边栏宽度可调整
1.手动实现拖放逻辑
2.使用 local storage 存储宽度位置

上中下结构,中间高度随父级高度自适应,头部和底部高度固定

1
2
3
4
5
<div className="app-layout1">
<div className="header">Header</div>
<div className="content">content</div>
<div className="footer">Footer</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<style>
.app-layout1 {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
}

.app-layout1 .header {
line-height: 60px;
border-bottom: 2px solid #fff;
}
.app-layout1 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 0;
right: 0;
}
.app-layout1 .footer {
border-top: 2px solid #fff;
line-height: 60px;
bottom: 0;
left: 0;
right: 0;
position: absolute;
}
</style>

导航布局,左边是导航栏,右边上中下结构

1
2
3
4
5
6
<div className="app-layout2">
<div className="header">Header</div>
<div className="sider">Sider</div>
<div className="content">Content</div>
<div className="footer">Footer</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
.app-layout2 {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
padding-left: 150px;
line-height: 60px;
}

.app-layout2 .header {
border-bottom: 2px solid #fff;
}
.app-layout2 .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 150px;
right: 0;
}
.app-layout2 .sider {
width: 150px;
position: absolute;
border-right: 2px solid #fff;
top: 0;
left: 0;
bottom: 0;
}
.app-layout2 .footer {
border-top: 2px solid #fff;
bottom: 0;
left: 150px;
right: 0;
position: absolute;
}

导航栏随鼠标滑动调整宽度

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import React from "react";
import { Button } from "antd";
import "./LayoutResize.css";

export default class LayoutResize extends React.PureComponent {
state = {
dragging: false,
startPageX: 0,
siderWidth: 150,
};

handleMouseDown = evt => {
this.setState({
dragging: true,
startPageX: evt.pageX,
});
};
handleMouseUp = () => {
this.setState({
dragging: false,
});
};
handleMouseMove = evt => {
let siderWidth = this.state.siderWidth + evt.pageX - this.state.startPageX;
if (siderWidth < 20 || siderWidth > 300) return;
this.setState({
siderWidth,
startPageX: evt.pageX,
});
};
render() {
const { dragging, siderWidth } = this.state;
const pxWidth = `${siderWidth}px`;
return (
<div className="app-layout-resize" style={{ paddingLeft: pxWidth }}>
<div className="header">Header</div>
<div className="sider" style={{ width: pxWidth }}>
Sider
</div>
<div className="content" style={{ left: pxWidth }}>
Content
</div>
<div className="footer" style={{ left: pxWidth }}>
Footer
</div>
<div
className="sider-resizer"
style={{ left: pxWidth }}
onMouseDown={this.handleMouseDown}
/>
{dragging && (
<div
className="resize-mask"
onMouseMove={this.handleMouseMove}
onMouseUp={this.handleMouseUp}
/>
)}
</div>
);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
.app-layout-resize {
width: 500px;
height: 400px;
position: relative;
background-color: #eee;
text-align: center;
padding-left: 150px;
line-height: 60px;
}

.app-layout-resize .header {
border-bottom: 2px solid #fff;
}
.app-layout-resize .content {
position: absolute;
bottom: 60px;
top: 60px;
left: 0;
right: 0;
}
.app-layout-resize .sider {
width: 150px;
position: absolute;
border-right: 2px solid #fff;
top: 0;
left: 0;
bottom: 0;
}
.app-layout-resize .footer {
border-top: 2px solid #fff;
bottom: 0;
left: 150px;
right: 0;
position: absolute;
}

.app-layout-resize .sider-resizer {
position: absolute;
left: 148px;
width: 6px;
top: 0;
bottom: 0;
cursor: col-resize;
}
.app-layout-resize .resize-mask {
background: rgba(0, 0, 0, 0);
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
cursor: col-resize;
}
My Little World

表单 & 列表

发表于 2018-12-04

表单功能点

  1. 初始数据,提交和跳转

表单各项配置的优化,可以通过对UI框架的API进行封装成高阶组件,然后在使用时,通过配置高阶组件的属性,将需要的配置项传递给UI框架API,减少代码冗余,配置清晰

提交后状态通过中间件返回promise判断接下来操作

2.
错误处理(可依靠UI框架进行判断)
动态表单元素(在传入高阶组件属性时采用函数返回值形式动态加载表单元素,在函数中处理要不要显示表单元素)
内容动态加载(在componentDidMount中进行数据请求,以及初始状态处理)

列表页功能点

搜索,数据缓存和分页

开发列表也要考虑的技术要点
1.如何翻页 (数据来自服务器还是客户端缓存)
2.如何进行内容搜索 (当前页面数据搜索还是服务器端数据搜索)
3.如何缓存数据
4.何时进行页面刷新

列表页数据通过store拿数据渲染,翻页,查找,刷新通过触发action然后从服务器拿到数据后,更新store,从而组件重新渲染

store 模型
listItems:当前页id集合数组[id1,id2]
byId:{id1:{id:id1,name:xxx},id2:{id:id2,name:xxx}}
keyword:关键字string
page:number 页数
fetchListPending:bool是否正在加载数据
FetchListError:OBJECT数据加载出错
listNeedReload:bool是否需要重新加载(做判断条件,例如编辑之后,设为true,在列表页拿到判断为true则重新获取列表数据)

URL设计
将页数和关键字当做路由参数传递在componentDidupdated中获取参数判断是否重新获取数据

缓存更新,加载状态,错误处理

通过store模型中的相关数据进行判断展示

页面数据需要来源多个请求的处理

页面数据来自多个请求
1.请求之间无依赖关系,可以并发进行
2.请求有依赖,需要依次进行 (promise)
3.请求完成之前,页面显示 Loading 状态
loading数据状态由当前数据推导确定

内容页的加载和缓存

内容页和列表页的数据关系
1.简单业务:列表页数据包含内容页的数据 (注意页面刷新情况数据的获取)
2.复杂业务:内容页数据需要额外获取 (每次进来都重新获取)
3.内容页数据的缓存(将数据放在store中,在store中拿数据)

基于React Router 实现分步操作

向导页面需要考虑的技术要点
1.使用 URL进行导航的好处 (可以直接刷新其中一个步骤)

  1. 表单内容存放的位置
  2. 页面状态如何切换

将表单元素放在一个统一的form中,通过路由判断第几步,进而显示不同的表单元素,需要注意的是,切换下一步时,当前组件消失,配置的数据也会消失,需要将当前所填数据进行保存,以便在返回上一步时有数据

集成第三方 JS 库的技术要点

1.使用 ref 获取原生 DOM 节点引用
2.手动将组件状态更新到 DOM 节点
(对于数据驱动UI展示的情况,第一次渲染和之后更新需要手动操作DOM的过程,将数据注入过程隔离出来,单独处理成一个函数,再当react 部分操作导致需要重新渲染时,再调用,将react状态映射到第三方DOM中)
3.组件销毁时移除原生节点 DOM 事件

基于路由实现菜单导航

1.菜单导航只是改变 URL 状态
2.根据当前 URL 显示菜单的当前状态(使用navLink标签实现,注意react-router和redux一起使用时,url发生变化,组建要重新render时,必须要让组件绑定到router的store上,即让react-router也一起渲染)

如何避免应用出现性能问题

1.了解常见的性能问题场景 (键盘输入,鼠标移动是否卡顿)
2.时刻注意代码的潜在性能问题 (何时拆分组件,有优化空间;能否高效更新,组件拆分是否够细,足够细的话,组件越接近纯函数,就越可能减少参与到diff操作的可能性,从而提高渲染速度)
3.注重可重构的代码 (代码耦合性低,几乎不依赖外界,也不被外界依赖,对于有重构可能性的代码保留重构空间,当其他优先级较高的性能问题解决后,再进行代码重构)
4.了解如何使用工具定位性能问题

网络性能优化:自动化按需加载

1.什么是按需加载 (切换页面时才加载相应页面,而不是一开始就将所有页面加载进来)
2.使用 Webpack 的 import API
3.使用 react-loadable 库实现 React 异步加载
利用分包加载,在配置路由时实现,在总路由中删除,防止打包到总包中

1
2
3
4
5
import loadable from 'react-loadable'
const listPage = loadable({
loader:()=>import ('./listPage'),
loading:()=><div>loading...</div>
})

listPage页面在加载该页时,会自动加载listPage的独立打包文件

使用 reselect 避免重复计算
reselect 可以创建自动缓存的数据处理流程,通过保存计算结果,避免重复计算

异步渲染的两个部分

时间分片
DOM操作的优先级低于浏览器原生行为,例如键盘和鼠标输入,从而保证操作的流畅。
(setstate导致的重新render等页面行为(滚动,输入)结束后再执行)
1.虚拟 DOM 的 diff 操作可以分片进行 (对操作进行序列化,然后进行合并或者优先级处理)
2.React 新 API: unstatble_deferredUpdates(低级别的setstate)
3.Chrome 新 API:requestIdleCallback(浏览器级别的api,告诉代码内存空闲(不再滚动或敲击键盘),可以进行一些之前优先级低的UI更新操作,时间分片基础)

渲染挂起
虚拟DOM节点可以等待某个异步操作的完成,并指定timeout后,才完成真正的渲染
(可以不再注意什么时候显示loading状态,取消loading状态,虚拟dom节点可以自己等待异步操作的完成,只要虚拟dom节点返回一个promise,渲染引擎就会等promise结束后再去render)

  1. 新内置组件:Timeout

  2. unstatble_deferUpdate

借助工具发现性能问题

1.使用 React DevTool 找到多余渲染
chrome 插件,可查看react应用的状态,帮助找到不必要的render,从而进行调优—> [highlight update]
2.使用 Chrome DevTool 定位性能瓶颈
什么样的组件花了多长时间做了什么样的渲染—>performance

My Little World

Rekit

发表于 2018-12-04

背景
一个独立功能通常需要多个文件组成 (组件,reducer,action,路由等文件)
代码模板很复杂 (reducer,action需要代码模板,实际上填写的逻辑只是一小部分)
重构极为困难
(需要改动的地方很多)
项目复杂后很难理解和维护(无法直观的看到他们之间的关系)

思路

Rekit:更好的代码导航

  1. 语义化的组织源代码文件
  2. 使用子 Tab来展示项目元素的各个部分
  3. 直观的显示和导航某个功能的所有依赖

Rekit: 一键生成项目元素(它是一个IDE)

  1. 直观的 UI 用于生成组件,action,reducer 等
  2. 模板代码遵循最佳实践(只需填入具体逻辑)
  3. 支持命令行方式创建项目元素

Rekit: 重构非常容易

  1. 右键菜单重命名或者删除某个项目元素
  2. 所有相关代码都会一次性重构从而保证一致性
  3. 详细的 log信息显示重构的完整修改

Rekit: 可视化的项目架构

  1. 项目总体架构的可视化图表
  2. 项目依赖关系的图表

Rekit 是如何工作的?

  1. 定义了基于feature 的可扩展文件夹结构(文件夹结构有一定规则,可以进行解析)
  2. 基于最佳实践生成代码和管理项目元素
  3. 提供工具和 IDE 确保代码和文件夹结构遵循最佳实践
    (减少工作量)

遵循最佳实践

1.以 feature 方式组织代码 (功能如果是必须的就放在已有模块底层实现,如果是原有功能增强的实现,则新拆分成功能模块)
2.拆分组件,action 和 reducer
3.拆分路由配置

通过代码自动生成保持一致性
1.文件夹结构一致性
2.文件名一致性
3.变量名一致性
4.代码逻辑的一致性

使用 React Router 管理路由授权

1.实现基础:React Router 的动态路由机制 (
不采用各个页面判断是否登录再redirect的方式跳转,路由配置(添加属性,那些可以在未登录下访问,或者哪些在未登录下不能访问)时就去动态监测当前用户是否登录,如果登录则全部路由生效;反之某些路由配置就不生效===>不生效处理,在解析router JSON数据时根据store中登录状态和路由配置的属性判断,未登录则跳转403,其子路由清空
)
2.区分受保护路由和公开路由
3.访问未授权路由时重定向到登录页面

My Little World

Redux

发表于 2018-12-04

为什么需要redux

原有组件间通信方式错综复杂,逐层传递,不明了,方便组件间通信
用store管理所有组件的状态

redux 特性

single source of truth:状态来源唯一;

可预测性:state +action = new state

纯函数更新Store:输出取决于输入,内部不会依赖任何除参数以外外部元素产生副作用

store 三个函数

getState()

dispatch(action)

subscribe(listener)

action 描述行为的对象集合

reducer 更新state的具体行为

几个工具函数

combineReducers 把多个reducer 合成一个新的reducer,并定义每个reducer在store中的对应节点

bindActionCreators 封装actionCreator和store.dispatch调用过程

1
2
3
4
5
6
7
8
9
10
function actionCreator(){
return {
type:'addItem',
text:'......'
}
}
store.dispatch(actionCreator())
===>
actionCreator = bindActionCreators(actionCreator,store.dispatch)
actionCreator()

connect 创建高阶组件,对store 特定属性进行监听和绑定,从而在这些属性变化时自动刷新组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
import React from "react";
import { bindActionCreators, createStore } from "redux";
import { Provider, connect } from "react-redux";

// Store initial state
const initialState = { count: 0 };

// reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case "PLUS_ONE":
return { count: state.count + 1 };
case "MINUS_ONE":
return { count: state.count - 1 };
case "CUSTOM_COUNT":
return { count: state.count + action.payload.count };
default:
break;
}
return state;
};

// Create store
const store = createStore(counter);

// Action creator
function plusOne() {
// action
return { type: "PLUS_ONE" };
}

function minusOne() {
return { type: "MINUS_ONE" };
}

export class Counter extends React.Component {
render() {
const { count, plusOne, minusOne } = this.props;//这三者通过connect关联store后获得
return (
<div className="counter">
<button onClick={minusOne}>-</button>
<span style={{ display: "inline-block", margin: "0 10px" }}>
{count}
</span>
<button onClick={plusOne}>+</button>
</div>
);
}
}
//组件需要数据,state是store.getState()获得的数据集合根节点
//性能问题:如果将store中的整个state绑定到组件上,那state上任何一个属性发生变化都会引起组件更新,所以向组件传递数据时,将数据绑定到尽可能的最小范围,实现只有绑定的数据变化时才引起组件更新
function mapStateToProps(state) {
return {
count: state.count
};
}
//组件中需要用到的触发函数
function mapDispatchToProps(dispatch) {
return bindActionCreators({ plusOne, minusOne }, dispatch);
}
//使用connect将redux和react 组件结合起来,形成高阶组件
const ConnectedCounter = connect(mapStateToProps, mapDispatchToProps)(Counter);

export default class CounterSample extends React.Component {
render() {
return (
<Provider store={store}> //根节点定义Provider,挂上store,其所有子节点均可以访问到
<ConnectedCounter />
</Provider>
);
}
}

主要流程

1.定义redux store中需要的东西:reducer,actionCreator
2.使用createStore 根据reducer创建store
3.定义组件,确定需要哪些数据和触发函数
4.根据组件需要,创建数据获取函数,封装触发函数相应actionCreator
5.使用connect结合redux和组件形成高阶组件在Provider 中使用

异步Action

异步 action不是特殊action而是多个同步 action的组合使用

一个AJAX的Action发出后,到达中间件,进行预处理,根据预处理的结果dispatch不同的action出去,给reducer,然后更新store

异步Action不是redux的一个概念而是action的一种设计模式

不同的action还是同步的action,只是异步action把这些同步的action放在了不同阶段去dispath

中间件
1.截获某种特定类型的action,进行特殊处理
2.发出action

如何组织action和reducer

‘标准’形式redux action的问题
1.所有Action放一个文件,会无限扩展
2.Action,Reducer分开,实现业务逻辑时需要来回切换
3.系统中有哪些Action不够直观
新的方式
单个action和reducer放在同一个文件,一个文件一个action
然后将所有action导入同一文件,所有reducer导入同一文件
1.易于开发:不用在action和reducer文件间来回切换
2.易于维护:每个action文件都很小,容易理解
3.易于测试:每个业务逻辑只需要对应一个测试文件
4.易于理解:文件名就是action名字,文件列表就是action列表
a sample
问题:拆分这么细的话,文件会多很多,对包的大小也会有影响

答:webpack 确实会为每个文件生成一小段固定的元代码,同样的代码行数,文件越多 bundle 确实会越大。但这个并不会成为性能瓶颈,两个原因:1. 拆分后代码数量不会显著增多,增加的元代码部分相比整个包的大小基本可以忽略;2. 生产环境普遍会启用 gzip,对于重复的元代码信息会被有效压缩,对比拆分前,gzip 后的包大小基本没有差异

不可变数据

redux在修改state时采用新对象替换老对象的思路,不在原来对象上做修改,在新对象中复制老对象,包含要修改的部分,通过对比新老对象的引用不同,引起节点更新—-redux运行基础

为何需要不可变数据
1.性能优化(通过对比新旧state不是同一个引用即可判断store发生了变化,从而不用进行深层遍历,比较具体的值,redux中的store都是不可变数据,每个节点都是不可变数据,当一个组件绑定在一个节点上,只需判断前后状态的引用是否相等,从而判断store是否发生变化,进而决定是否要更新组件)
2.易于调试和跟踪(store变化时可以看到前后状态,便于调试)
3.易于推测(任何时刻可以知道是什么引起store发生变化,根据action前后的状态判断,action是否被进行了正确处理)

如何操作不可变数据
1.原生写法{…},Object.assign() 性能最好

1
2
3
4
5
6
const state = {
filter:'completed',
todos:['learn react']
}
const newState1 = {...state,todos:[...state.todos,'learn redux']}
const newState = Object.assign({},state,{todos:[...state.todos,'learn redux']})

2.immutability-helper 适合需要深层次节点处理,需要引入类库,熟悉相应语法

1
2
3
4
5
6
import update from 'immutability-helper'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = update(state,{todos:{$push:['learn redux']})

3.immer 可以像操作原对象一样生成新对象,性能最差(当节点层数多,结构复杂时,需要为每个属性建立代理),适合应用程序小,场景简单的情况

1
2
3
4
5
6
7
8
import produce from 'immer'
const state = {
filter:'completed',
todos:['learn react']
}
const newState = produce(state,draftState =>{
draftState.todos.push('learn redux')
})

My Little World

路由

发表于 2018-12-04

为什么需要路由

1.单页应用需要进行页面切换
2.通过URL可以定位到页面
3.更有语义的组织资源

基本原理

在组件容器里根据URL决定显示什么样的组件

REACT router特性(和后端路由对比)

1.声明式路由定义

通过react组件标签进行声明,可以放在任何地方,不需要具体的路由表进行声明

2.动态路由

传统路由一旦配置了它就是一个配置文件,成为一个静态文件
而react router的路由是页面在render的时候才会被解析的,有相应路由的标记标签就是有相应的配置,没有标签就没有相应的配置

三种路由实现方式

  1. url路径:通过改变URl更改视图

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import React from "react";
    import { BroswerRouter as Router, Route, Link } from "react-router-dom";

    const Home = () => <h1>Home</h1>;
    const Hello = () => <h1>Hello</h1>;
    const About = () => <h1>About Us</h1>;

    export default class RouterSample extends React.PureComponent {
    render() {
    return (
    <Router>
    <div>
    <ul id="menu">
    <li>
    <Link to="/home">Home</Link>
    </li>
    <li>
    <Link to="/hello">Hello</Link>
    </li>
    <li>
    <Link to="/about">About</Link>
    </li>
    </ul>

    <div id="page-container">
    <Route path="/home" component={Home} />
    <Route path="/hello" component={Hello} />
    <Route path="/about" component={About} />
    </div>
    </div>
    </Router>
    );
    }
    }
  2. hash路由:使用Router 的HashRouter组件 进行路由容器包裹,切换路由时,
    具体的路径会被放在URl的#后面,通过改变hash更变视图《兼容低版本浏览器》

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    import React from "react";
    import { HashRouter as Router, Route, Link } from "react-router-dom";

    const Home = () => <h1>Home</h1>;
    const Hello = () => <h1>Hello</h1>;
    const About = () => <h1>About Us</h1>;

    export default class RouterSample extends React.PureComponent {
    render() {
    return (
    <Router>
    <div>
    <ul id="menu">
    <li>
    <Link to="/home">Home</Link>
    </li>
    <li>
    <Link to="/hello">Hello</Link>
    </li>
    <li>
    <Link to="/about">About</Link>
    </li>
    </ul>

    <div id="page-container">
    <Route path="/home" component={Home} />
    <Route path="/hello" component={Hello} />
    <Route path="/about" component={About} />
    </div>
    </div>
    </Router>
    );
    }
    }
  3. 存路由:使用react-router 的MemoryRouter组件进行路由容器包裹,路由信息放在内存中管理,URL不变的情况下,即可进行视图切换

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    import React from "react";
    import { HashRouter as Router, Route, Link } from "react-router-dom";
    import { MemoryRouter } from "react-router";

    const Home = () => <h1>Home</h1>;
    const Hello = () => <h1>Hello</h1>;
    const About = () => <h1>About Us</h1>;

    export default class RouterSample extends React.PureComponent {
    render() {
    return (
    <MemoryRouter>
    <div>
    <ul id="menu">
    <li>
    <Link to="/home">Home</Link>
    </li>
    <li>
    <Link to="/hello">Hello</Link>
    </li>
    <li>
    <Link to="/about">About</Link>
    </li>
    </ul>

    <div id="page-container">
    <Route path="/home" component={Home} />
    <Route path="/hello" component={Hello} />
    <Route path="/about" component={About} />
    </div>
    </div>
    </MemoryRouter>
    );
    }
    }

基于路由配置进行资源组织 好处

1.实现业务逻辑的松耦合
2.易于扩展,重构和维护
3.路由层面实现Lazy Load

REACT Router API

1.:普通链接,会触发浏览器刷新
类似a标签,但是不会触发浏览器的刷新,点击时router会接管导航,对切换进行处理不会传递到浏览器,让其进行真正的页面切换
to属性代表链接到的URl的地址
2.:类似 Link但是会添加当前选中状态
可以添加activeClassName属性,当当前链接符合to属性值时,显示相应的样式 还有其他属性,详情请看react-router文档

1
<NavLink to="/faq" activeClassName='selected'>FAQs</NavLink>

3.:满足条件时提示用户是否离开当前页面
切换页面时,跟用户提供一个确认的操作

1
2
3
4
5
import { Prompt } from "react-router";
<Prompt
when={formIsHalfFilledOut}
message='Are you sure you want to leave'
/>

4.重定向当前页面,例如登录判断

1
2
3
4
5
6
7
8
import { Redirect,Route } from "react-router";
<Route exact path='/' render={()=>(
loggedIn?(
<Redirect to='/dashboard' />
) : (
<PublicHomePage/>
)
)}/>

5.:路由配置的核心标记,路径匹配时显示对应组件
path:路由
component:相应要显示的组件
exact:是否精准配置path
多个route path都符合当前路由时,那相应组件都会进行显示

1
<Route exact path="/" component={Home} />

6.:只显示第一个匹配的路由
找到一个匹配的路径就只显示这个路径相应的组件,其他组件不显示

1
2
3
4
5
6
7
import { Switch,Route } from "react-router";
<Switch>
<Route path="/home" component={Home} />
<Route path="/hello" component={Hello} />
<Route path="/about" component={About} />
<Route component={Nomatch} />
</Switch>

参数传递

通过URL传递参数 :
获取参数:this.props.match.params
页面状态尽量通过URl参数定义,方便页面间跳转时数据传递,否则需要将数据转化成组件内部state进行渲染,过程复杂

套嵌路由

1.每个React组件都可以时路由容器
2.React Router的声明式语法可以方便的定义嵌套路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
import React from "react";
import {
BrowserRouter as Router,
Route,
Link
} from "react-router-dom";

const Category = ({ match }) => (
<h1>Sub Category {match.params.subId}</h1>
);

const SubCategory = ({ match }) => (
<div>
<h1>Category {match.params.id}</h1>

<ul id="menu">
<li>
<Link to={`/category/${match.params.id}/sub/1`}>
Sub Category 1
</Link>
</li>
<li>
<Link to={`/category/${match.params.id}/sub/2`}>
Sub Category 2
</Link>
</li>
<li>
<Link to={`/category/${match.params.id}/sub/3`}>
Sub Category 3
</Link>
</li>
</ul>
<div id="page-container-2">
<Route
path="/category/:id/sub/:subId"
component={Category}
/>
</div>
</div>
);

export default class NestedRoute extends React.PureComponent {
render() {
return (
<Router>
<div>
<ul id="menu">
<li>
<Link to="/category/1">Category 1</Link>
</li>
<li>
<Link to="/category/2">Category 2</Link>
</li>
<li>
<Link to="/category/3">Category 3</Link>
</li>
</ul>

<div id="page-container">
<Route
path="/category/:id"
component={SubCategory}
/>
</div>
</div>
</Router>
);
}
}

My Little World

构建

发表于 2018-12-04

UI库

ant design 适合企业级应用,复杂交付,密集数据展示
material UI:时尚,花哨,具有的美观性,更适合开发面向消费者的应用(google版原始react实现)
sematic UI:把UI当做一种language来描述(jquery版原始react实现)

选择UI库的考虑因素

  1. 组件库是否齐全(齐全的话可以减少很多工作量,不用自己实现)
  2. 样式风格是否符合业务需求(企业级应用一般要求简洁明了,对于密集性数据展示比较合理;对于移动端面向消费者一般要求好看,button,字体比较大)
  3. API 设计是否便捷和灵活(使用起来是否方便,从而会影响开发效率)
  4. 技术支持是否完善(技术文档是否齐全;github上提issue时,能不能得到快速解答)
  5. 开发是否活跃(是否有稳定团队在进行快速迭代和维护,在遇到BUG或者需要新功能时可以得到满足)

使用next.js 构建react 同构应用

同构应用:浏览器第一次向服务器请求页面时,服务器返回解析好的页面,不需要浏览器执行js来进行渲染(这样可以加快页面首次打开的速度),之后页面的操作/切换,像单页面一样,不需要浏览器刷新,均由前端完成,包括UI渲染,页面路由切换等,不需要再向服务端请求页面

next.js的几个规则
1.页面就是pages目录下的一个组件
所有页面放在pages文件夹下,一个页面即一个组件,文件名和文件路径对应路由路径
2.static目录映射静态文件(图片)
3.page具有特殊静态方法 getInitialProps
next.js提供给react组件初始化props的方法

命令行装包:

1
npm install --save react react-dom next

在页面中使用其它 react 组件
1.页面也是标准的node模块,可使用其它 react组件
2.页面会针对性打包,仅包含其引入的组件
,不会加载其他额外的资源

所有组件放在components文件夹下,在pages文件夹下的页面js中使用时,直接用路径import进去

使用Link实现同构路由

1.使用 next/link 定义链接

1
2
3
4
5
import Link from 'next/link'
export default ()=>
<div>
<Link href='/about'><a>here</a></Link>
</div>

2.点击链接时页面不会刷新
如果使用a标签会产生刷新

3.使用 prefatch预加载目标资源
如果不添加该属性,则链接对应的组件,在切换的时候再加载相应页面打包的内容,有该属性,则next.js 打包时就会获取所有链接对应的组件,会预加载所有的js内容,从而提高页面切换速度,但是并不会预加载需要从服务器端API请求的数据

4.使用 replace 属性替换 url
替换当前页在路由中的history,无法后退回当前页

动态加载页面

1
2
3
4
5
6
import dynamic from 'next/dynamic'
const DynamicComponet=dynamic(import('../components/hello'))
export default ()=>
<div>
<DynamicComponet/>
</div>

页面在加载时,除了加载整个页面的main.js,还会加载动态加载的组件的js包,当这个动态组件被render的时候才会被加载

测试

react 让前端单元测试变得容易
1.react 应用很少需要访问浏览器 API
2.虚拟 Dom 可以在 NodeJs 环境运行和测试 (不需要浏览器环境,在内存中render虚拟DOM即可完成测试)
3.Redux隔离了状态管理,纯数据层单元测试

单元测试涉及的工具
1.Jest: Facebook 开源的 JS 单元测试框架 (零配置,可以直接开始测试)
2.JS dom浏览器环境的 NodeJS 模拟 (一个在nodejs中可以模拟浏览器的library)
3.Enzyme:React组件渲染和测试 (在nodejs中渲染虚拟DOM进行测试,通过进行不同程度的render(shallow Rendering,Full Rendering,Static Rendering,进行不同程度的测试,比如利用shallow Rendering浅渲染测试渲染结果是否符合预期的DOM结构)
4.Nock: 模拟 HTTP请求 (模拟得到请求返回数据)
5.Sinon: 函数模拟和调用跟踪 (例如测试组件UI时,跟踪响应函数有没有被调用,调用几次,而不是去测试响应函数是否符合预期,测试响应函数是否符合预期属于响应函数测试范畴。集成在JEST中)
6.istanbul:单元测试覆盖率(通过对已有代码的修改和埋点去判断相关代码有没有被执行到)

开发调试工具

eslint
1.使用 .eslintrc 进行规则的配置
2.使用 airbnb 的 JavaScript 代码风格

检查语法风格,拼写错误,会报错,保证代码的一致性

Prettier
vscode 的一款插件,根据一定规则做代码格式化 .eslintrc
1.代码格式化的神器
2.保证更容易写出风格一致的代码
不会报错,会直接根据规则将代码格式化

React DevTool chrome插件
定位组件,可以以组件树的形式查看DOM结构,
勾选highlight updates可看到需要更新的组件

Redux DevTool chrome插件
可查看action和action引起的变化
time machine 功能,可以回溯action,方便观察loading过程,进而对loading进行优化
可自动生成测试代码

理想架构

易于开发(但可能会不容易扩展和维护)
1.开发工具是否完善 (采用的技术栈是否有相应的开发工具来支持)
2.生态圈是否繁荣 (是否有类似项目遇到过相同问题可以借鉴)
3.社区是否活跃(提问有人回答)

易于扩展
1.增加新功能是否容易
2.新功能是否会显著增加系统复杂度(系统放大之后会难以扩展和维护,考虑引进新功能时,如何架构能保证减少系统复杂度的提高)

易于维护
1.代码是否容易理解 (基于一定的最佳实践规范去编写,保证多人开发有一定的规则)
2.文档是否健全(通过注释,架构图,具体文档描述项目中的一些关键点,方便后续工作人员查看,也方便日后回头开发能快速理解关键点,来维护已有项目)

易于测试
1.功能的分层是否清晰 (UI,数据层之间依赖少)
2.副作用少 (模块高内聚,对外部依赖少)
3.尽量使用纯函数(输入决定输出,确定)

易于构建
1.使用通用技术和架构 (保证现有项目不需要定制化,就可以打包部署)
2.构建工具的选择(webpack ,roolup,使用常用技术栈,减少额外配置)

大型前端应用需要拆分复杂度原因
当项目增加更多功能的时候,项目复杂度并不会指数级的上升,始终保持在一个可控的范围之内

拆分复杂度技术架构

从功能上进行区分,将业务逻辑拆成高内聚松耦合的模块,每个模块负责一个功能,拥有自己的componet,action和reducer,这样即使当一个功能需要删除时,只要删除相应的模块即可,也保证其他模块不受影响;路由不再由一个文件统一定义,每个feature自己定义一个路由,然后由route的loader把每个模块的路由文件加载进根结点的routing文件

技术上的component,路由,reducer是按照功能的方式组织在模块上

如何组织 component,action 和reducer
文件夹结构
• 按 feature 组织源文件
• 组件和样式文件同一级
• React 单独文件夹
• 单元测试保持同样目录结构放在 tests 文件夹

组件和样式组织
一个功能的组件js和样式文件放在一个模块中,再由一个index.js引入各个模块的组件js文件,一个style文件引入各个模块的样式文件,进行统一管理输出,打包时就只是打包index.js和style文件
注意:
如果组件中引入样式文件,那么当这个组件被重复使用时,样式文件会被多次引入,打包时就会多次进行打包,造成不必要的代码冗余
使用上述加载方式,样式文件独立加载,js文件只需要加载它所需要的资源,就不会发生多次加载

action 和 reducer组织
一个模块的,action 和 reducer 一一对应放在同一文件,再由一个actions.js引入所有action,一个reducers.js引入所有reducer,这两个文件只是充当loader的角色
好处:action 和 reducer 非常小,不会被无限的去扩展,更容易理解

CONSTANTS.js中命名的常量一功能名开头,避免与其他模块常量命名冲突

再由rootreducer加载所有模块下的reducer,进行combineReducers挂载,实现全局使用

优点:一个模块内部是高内聚的,各个模块之间是松耦合的

主要思想:使用 root loader 加载 feature的各个资源

路由配置
每个模块(页面级)路由通过export JSON格式数据 ,自己配,再loader到根路由,由根路由统一处理解析json格式为react Router的声明式形式

每个模块都有都有自己的专属路由配置
顶层路由使用JSON配置更易维护和理解

My Little World

开发思想

发表于 2018-12-04

源码
git仓库

传统框架存在问题

1.传统 UI操作关注太多细节
DOM API 繁多,需要手动调用,且要经过多步骤进行更新
react 解决办法:始终整体‘刷新’页面 ,无需关心细节,(不管谁是哪部分新增或更改的,根据现在状态整体刷新页面)

2.应用程序状态分散在各处f难以追踪和维护
传统数据模型存在问题:传统 MVC结构复杂,难以扩展和维护
react 解决办法:Flux 单向数据流

创建组件
1.创建静态 UI
2.考虑组件的状态组成 :是由state内部维护还是用外部传进来的props维护
3.考虑组件的交互方式:内部进行了操作,如何告知/把状态暴露到父组件

受控组件VS非受控组件

受控组件: 表单元素状态由使用者维护
组件发生变化,利用onchange将状态给到到父组件(使用者),父组件更新状态,再通过props传递状态给组件,组件重新渲染

非受控组件:表单元素状态 DOM自身维护

创建原则:单一职责原则

1.每个组件只做一件事
2.如果组件变得复杂,那么应该拆分成小组件
拆分成小组件好处:
a.降低复杂度;
b.提高性能,如果一个组件足够大之后,任何一种状态变化,都会引起整个组件刷新,
拆分成小组件后,未涉及到的小组件,没有状态变化,就不会刷新

数据状态管理:DRY(Don’t Repeat Yourself)原则

1.能计算得到的状态就不要单独存储 :
2.组件尽量无状态,所需数据通过 props 获取

JSX

本质:不是模板语言,而是一种语法糖(相当于使用React.createElement创建组件)
优点:
1.声明式创建界面的直观(可以使用自定义和原生标签直接进行创建,方便构建UI)
2.代码动态创建界面的灵活 (节点的创建,属性更改,消失完全可以向写JS一样进行操作)
3.无需学习新的模板语言(JS+JSX特性)
约定:自定义组件以大写字母开头
1.react 认为小写的 tag是原生 dom 节点,如 div
2.大写字母开头为自定义组件
3.JSX 标记可以直接使用属性语法,例如<meau.Item />,使用属性语法,可以不以大写字母开头
JSX防注入攻击
React DOM 在渲染之前默认会 过滤 所有传入的值。它可以确保你的应用不会被注入攻击。
所有的内容在渲染之前都被转换成了字符串。这样可以有效地防止 XSS(跨站脚本) 攻击

生命周期方法

constructor
js标准类的构造函数
1.用于初始化内部状态,很少使用(初始化工作一般会在其他声明周期内使用)
2.唯一可以直接修改state的地方(可以这么写this.state.xxx = ‘abc’)

getDerivedStateFromProps
react 16.3新引入的API
从属性props初始化内部状态state
1.当 state 需要从 props 初始化时使用
2.尽量不要使用:维护两者状态一致性会增加复杂度
如果state需要从props获得,一般使用props可以通过动态计算得到,如果用state进行单独存储,就有必要时刻维护二者的一致性
维护一致性则增加了复杂度,容易出BUG
3.每次 render都会调用
该方代替原来的componentwillreceiveProps方法
4.典型场景:表单控件获取默认值
初始值来自props,用户输入后为用户输入的值

componentDidMount
1.UI渲染完成后调用
2.只执行一次
3.典型场景:获取外部资源(只需要获取一次资源)

componentWillUnmount
1.组件移除时被调用
2.典型场景:资源释放

getSnapshotBeforeUpdate
1.在页面 render之前调用,state 已更新
2.典型场景:获取 render之前的 DOM 状态

componentDidUpdate
捕获每一次更新
1.每次 UI 更新时被调用
2.典型场景:页面需要根据 props 变化重新获取数据

shouldComponentUpdate
1.组件决定 Virtual DOM是否要重绘
2.一般可以由 PureComponent 自动实现
3.典型场景:细节性能优化

在react 16.3中去掉的方法有
componentWillMount
componentWillReceive
componentWillUpdate

状态更新可能是异步的

1
2
3
4
5
6
7
8
//此代码可能无法更新计数器
this.setState({
counter: this.state.counter + this.props.increment,
});
//更改为以下写法即可
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));

1.算法复杂度O(n):通过广度优先分层比较/遍历实现线性算法

2.虚拟 DOM如何计算 diff
虚拟 DOM的两个假设
a.组件的 DOM结构是相对稳定的
b.类型相同的兄弟节点可以被唯一标识
根结点比较
属性变化及顺序:利用标识调整位置
节点类型发生变化:删除原有结点,构建新类型结点
节点跨层移动:删除原有结点,在应有(新)位置构建相同类型新结点

3.key属性的作用:提高性能

组件复用设计模式

1.高阶组件

传入原始组件,结合生命周期创建一些额外的功能,然后附加到原始组件上,返回增加功能后的原始组件
额外的功能一般都是一些通用的功能,避免在原始组建上增加额外功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import React from "react";

export default function withTimer(WrappedComponent) {
return class extends React.Component {
state = { time: new Date() };
componentDidMount() {
this.timerID = setInterval(() => this.tick(), 1000);
}

componentWillUnmount() {
clearInterval(this.timerID);
}

tick() {
this.setState({
time: new Date()
});
}
render() {
return <WrappedComponent time={this.state.time} {...this.props} />;
}
};
}

2.函数子组件

将子组件以函数返回值形式作为children属性传入原始组件,在原始组件中,在需要显示原始组件的地方执行函数,
或者在合适的生命周期中执行函数拿到多个子组件,再决定在什么位置显示不同的子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//写法一
<Mouse children={mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}/>
//写法二
<Mouse>
{mouse => (
<p>The mouse position is {mouse.x}, {mouse.y}</p>
)}
</Mouse>

//在原始组件应用子组件
class Mouse extends React.Component{
render(){
<div>
{this.props.children({x:50,y:100})}
</div>
}
}

context API

应用场景:全局性的一些字段,如theme的切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import React from "react";

const enStrings = {
submit: "Submit",
cancel: "Cancel"
};

const cnStrings = {
submit: "提交",
cancel: "取消"
};
//传入原始数据/默认值,创建context
const LocaleContext = React.createContext(enStrings);

class LocaleProvider extends React.Component {
state = { locale: cnStrings };
toggleLocale = () => {
const locale =
this.state.locale === enStrings
? cnStrings
: enStrings;
this.setState({ locale });
};
render() {
return (
// context的 Provider属性是一个组件,包裹需要用到context数据子组件的组件,其value值为相应在子组件中需要用到的数据
<LocaleContext.Provider value={this.state.locale}>
<button onClick={this.toggleLocale}>
切换语言
</button>
{this.props.children}
</LocaleContext.Provider>
);
}
}

class LocaledButtons extends React.Component {
render() {
return (
//context的 Consumer属性也是一个组件,包裹需要使用context数据的子组件,
<LocaleContext.Consumer>
{locale => (//local 即Context.Provider 的value值
<div>
<button>{locale.cancel}</button>
&nbsp;<button>{locale.submit}</button>
</div>
)}
</LocaleContext.Consumer>
);
}
}

export default () => (
<div>
<LocaleProvider>
<div>
<br />
<LocaledButtons />//使用context数据的组件必须为Provider组件的子组件,
</div>
</LocaleProvider>
<LocaledButtons />//如果子组件在Context.Provider形成组件的外面使用,则只会应用默认数据,不会随Value属性发生变化
</div>
);

为什么需要脚手架

react:进行UI开发
redux:进行状态的管理
react/router:进行路由的管理
babel:把最新的js特性翻译成兼容浏览器的旧的JS语法
webpack:进行打包
eslint:语法检查
因而需要引入以上几个技术栈的几十个个package,不但要了解,还要知道如何让进行相应的配置
在开启一个项目时,配置过程做完一次基本/只要实现一次,就基本可以复用,用来再开启新项目
所以为方便开发,用脚手架实现配置过程

脚手架有:
Create React App 基础(webpack,)
Rekit 开源项目
Codesandbox 在线开发工具
dva 蚂蚁金服的一套脚手架工具

为什么需要打包

1.编译ES6语法特性,编译JSX(保证浏览器可以兼容执行)
2.整合资源,例如图片,Less/Sass,svg
3.优化代码体积(对变量名进行缩短,去除注释,去除空格,去除不必要的变量等等)

webpack
将项目中所有资源进行整合,将每种资源(js,png,sass…)使用插件的方式载入对应的Loader,
loader能够决定自己支持什么样的语法,loader的输出是可以执行的js语言,可以兼容多平台多版本浏览器
可以部署的语言

打包注意事项
1.设置nodejs环境为production
开发环境可能需要检查输入的参数的类型,语法,拼写等问题,但生产环境不需要

2.禁用开发时专用代码,比如logger
打包时可以根据环境判断决定是否执行一定的代码,所以开发用到的代码在打包时不应被打到生产环境,要做区分
3.设置应用根路径
告诉应用到哪里获取地址

My Little World

命令模式

发表于 2018-11-08

应用情景

有时候需要向某些对象发送请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是什么。
此时希望用一种松耦合的方式设计软件,使得请求发送者和请求接收者能够消除彼此之间的耦合关系

主要思想

将命令接收者的具体执行函数存入 命令堆栈,命令发起者需要执行/撤销动作时,对堆栈进行处理
命令接收者可以以闭包形式被执行,也可以作为对象属性被执行到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
//闭包形式
var setCommand = function(btn,fn){
btn.onclick = function(){
fn()
}
}
var MenuBar = {
refresh:function(){
console.log('refresh menu')
}
}
var RefreshMenuBarCommand = function(reciver){
return function(){reciver.refresh()}
}
var refreshMenuBarCommand = RefreshMenuBarCommand(Menubar)
setCommand(btn1,refreshMenuBarCommand)

//宏命令,组装一个可以一次性触发多个命令的命令;属性形式,统一使用execute触发
var MacroCommand = function(){
return {
commandsList:[],
add:function(command){
this.commandsList.push(command);
},
execute:function(){
for(var i=0,cammand;command = this.commandsList[i++];){
command.execute();
}
},
undo:function(){
for(var i=0,cammand;command = this.commandsList[i++];){
command.undo();
}
},

}
}
var closeDoorCom ={
execute:function(){
console.log('close door')
},
undo:function(){console.log('open door')}
}
var openTvCom ={
execute:function(){
console.log('open TV')
},
undo:function(){console.log('close Tv')}
}
var macroCommand = MacroCommand()
macroCommand.add(closeDoorCom)
macroCommand.add(openTvCom)
macroCommand.execute()
macroCommand.undo()

与策略区别

策略模式指向问题域更小,所有策略对象目标一致,他们只是达到这个目的的不同手段,内部实现针对‘算法’
命令对象解决的目标更具发散性,还可以完成撤销,排队等功能

My Little World

订阅发布模式

发表于 2018-11-08

主要思想

通过逻辑处理函数/对象event,使事件订阅和发布过程忽略事件的具体处理过程
订阅时只需要给到event要订阅的事件A,和响应函数,
发布时给到event事件A被触发和相关参数,即可执行订阅时的响应函数
event 充当中介者的角色,管理事件响应的存储和执行

1.简单实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
let event = {
caches:{},
on:function(name,fn){
if(!this.caches[name]){
this.caches[name] = []
}
this.caches[name].push(fn)
},
emit:function(name,...args){
if(!this.caches[name]){return}
for(let i=0;i<this.caches[name].length;i++){
let fn=this.caches[name][i]
fn(args)
}
},
remove:function(name,fn){
if(this.caches[name].length<1){return}
for(let i=0;i<this.caches[name].length;i++){
let fn1=this.caches[name][i]
if(fn === fn1){
this.caches[name].splice(i,1)
}
}
}
}
let fn1 = (val)=>{console.log(val,1)}
let fn2 = (val)=>{console.log(val,2)}
event.on('aaa',fn1)
event.on('aaa',fn2)
event.emit('aaa','fire!')

2.全局事件冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
var Event = (function(){ 
var global = this,
Event,_default = 'default';
Event = function(){
var _listen,
_trigger,
_remove,
_slice = Array.prototype.slice,
_shift = Array.prototype.shift,
_unshift = Array.prototype.unshift,
namespaceCache = {},
_create,
find,
each = function( ary, fn ){
var ret;
for ( var i = 0, l = ary.length; i < l; i++ ){
var n = ary[i];
ret = fn.call( n, i, n);
}
return ret;
};

_listen = function( key, fn, cache ){
if ( !cache[ key ] ){
cache[ key ] = [];
}
cache[key].push( fn );
};

_remove = function( key, cache ,fn){
if ( cache[ key ] ){
if( fn ){
for( var i = cache[ key ].length; i >= 0; i-- ){
if( cache[ key ][i] === fn ){
cache[ key ].splice( i, 1 );
}
}
}else{
cache[ key ] = [];
}
}
};

_trigger = function(){
var cache = _shift.call(arguments),
key = _shift.call(arguments),
args = arguments,
_self = this,
ret,
stack = cache[ key ];

if ( !stack || !stack.length ){
return;
}

return each( stack, function(){
return this.apply( _self, args );
});
};

_create = function( namespace ){
var namespace = namespace || _default;
var cache = {},
offlineStack = [], // 离线事件
ret = {
listen: function( key, fn, last ){
_listen( key, fn, cache );
if ( offlineStack === null ){
return;
}
if ( last === 'last' ){
offlineStack.length && offlineStack.pop()();
}else{
each( offlineStack, function(){
this();
});
}

offlineStack = null;
},

one: function( key, fn, last ){
_remove( key, cache );
this.listen( key, fn ,last );
},

remove: function( key, fn ){
_remove( key, cache ,fn);
},
trigger: function(){
var fn,
args,
_self = this;

_unshift.call( arguments, cache );
args = arguments;
fn = function(){
return _trigger.apply( _self, args );
};

if ( offlineStack ){
return offlineStack.push( fn );
}
return fn();
}
};
return namespace ?( namespaceCache[ namespace ] ? namespaceCache[ namespace ] : namespaceCache[ namespace ] = ret ): ret;
};

return {
create: _create,
one: function( key,fn, last ){
var event = this.create( );
event.one( key,fn,last );
},
remove: function( key,fn ){
var event = this.create( );
event.remove( key,fn );
},
listen: function( key, fn, last ){
var event = this.create( );
event.listen( key, fn, last );
},
trigger: function(){
var event = this.create( );
event.trigger.apply( this, arguments );
}
};
}();

return Event;

})();

My Little World

AOP(切面)编程/装饰者模式

发表于 2018-11-07

定义

在不改变对象自身的基础上,在程序运行期间给对象动态的添加职责

主要思想

在每次要执行具体函数B时,先执行A,在每次执行完B之后都执行C,
以此类推,有必要的话,在C执行完再执行D,从而形成执行链

具体实现

先用一个temp函数包住AB,再用一个final函数包住temp和C,这样,在执行final函数时,会按顺序A-B-C执行

1.使用普通函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
let before=(fn,beforeFn)=>{
return (...args)=>{
beforeFn(args)
return fn(args)
}
}
let after = (fn,afterFn)=>{
return (...args)=>{
let ret = fn(args)
afterFn(args)
return ret
}
}

let a = ()=>{console.log(2)}
a = before(a,()=>{console.log(1)})
a = after(a,()=>{console.log(3)})
a() //1,2,3

2.改造Function对象,可以实现链式添加职责

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Function.prototype.before = function(beforeFn){
return ()=>{
beforeFn(arguments);
return this(arguments);
}
}
Function.prototype.after = function(afterFn){
return ()=>{
let ret = this(arguments);
afterFn(arguments);
return ret;
}
}
var func = function (){
console.log(2);
return 15}
func = func.before(()=>{console.log(1)}).after(()=>{console.log(3)})
func() //1,2,3 返回15

应用

1.数据统计上报
将点击响应事件包含的上报动作隔离出来,以after的顺序,装饰到点击事件响应函数上

2.动态改变函数参数
组装ajax请求参数时,在请求函数前装饰before函数,进行不同参数参加处理

3.插件式表单验证
在submit函数前装饰before函数进行不同条件验证

区分代理模式

代理模式的意图是当直接访问本体不方便或者不符合需要时,为这个本体提供一个替代者,
本体定义了关键功能,而代理提供或拒绝对它的访问,或者在访问本体之前做一些额外的事情
通常只有一层代理-本体的引用

AOP的作用用于对对象加入行为,一开始不能确定对象的全部功能时,
经常会形成一条装饰链

1…121314…26
YooHannah

YooHannah

260 日志
1 分类
23 标签
RSS
© 2025 YooHannah
由 Hexo 强力驱动
主题 - NexT.Pisces