基于React的衍生库
immutablejs
Immutable数据就是一旦创建,就不能更改的数据。每当对Immutable对象进行修改的时候,就会返回一个新的Immutable对象,以此来保证数据的不可变
有人说 Immutable 可以给 React 应用带来数十倍的提升,也有人说 Immutable 的引入是近期 JavaScript 中伟大的发明,因为同期 React 太火,它的光芒被掩盖了。这些至少说明 Immutable 是很有价值的。
Immutable的优点:
1.降低复杂度,避免副作用
2.节省内存。Immutable采用了结构共享机制,所以会尽量复用内存
3.方便回溯。Immutable每次修改都会创建新对象,且对象不变,那么变更记录就能够被保存下来。应用的状态变得可控、可追溯,方便撤销和重做功能的实现
4.函数式编程。Immutable本身就是函数式编程中的概念。纯函数式编程比面向对象更适用于前端开发,因为只要输入一致,输出必然是一致的,这样开发的组件更易于调试和组装
5.丰富的API
JavaScript 中的对象一般是可变的(Mutable),因为使用了引用赋值,新的对象简单的引用了原始对象,改变新的对象将影响到原始对象,比如
1 2 3 4 5
| var obj = { a: 1, b: 2 };var obj1 = obj;obj1.a = 999; obj.a
|
改变了obj1.a的值,同时也会更改到obj.a的值。
一般的解法就是使用「深拷贝」(deep copy)而非浅拷贝(shallow copy),来避免被修改,但是这样造成了 CPU和内存的浪费.
immutable可以很好地解决这些问题
Immutable Data 就是一旦创建,就不能再被更改的数据。对 Immutable 对象的任何修改或添加删除操作都会返回一个新的 Immutable 对象。Immutable 实现的原理是 Persistent Data Structure(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免 deepCopy 把所有节点都复制一遍带来的性能损耗,Immutable 使用了 Structural Sharing(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享。
Immutable 的几种数据类型:
- List: 有序索引集,类似JavaScript中的Array。
- Map: 无序索引集,类似JavaScript中的Object。
- OrderedMap: 有序的Map,根据数据的set()进行排序。
- Set: 没有重复值的集合。
- OrderedSet: 有序的Set,根据数据的add进行排序。
- Stack: 有序集合,支持使用unshift()和shift()添加和删除。
- Range(): 返回一个Seq.Indexed类型的集合,这个方法有三个参数,start表示开始值,默认值为0,end表示结束值,默认为无穷大,step代表每次增大的数值,默认为1.如果start = end,则返回空集合。
- Repeat(): 返回一个vSeq.Indexe类型的集合,这个方法有两个参数,value代表需要重复的值,times代表要重复的次数,默认为无穷大。
- Record: 一个用于生成Record实例的类。类似于JavaScript的Object,但是只接收特定字符串为key,具有默认值。
- Seq: 序列,但是可能不能由具体的数据结构支持。
- Collection: 是构建所有数据结构的基类,不可以直接构建。
方法:
fromJS():
作用
: 将一个js数据转换为Immutable类型的数据 用法
: fromJS(value, converter)
简介
: value是要转变的数据,converter是要做的操作。第二个参数可不填,默认情况会将数组准换为List类型,将对象转换为Map类型,其余不做操作。
is()
作用
: 对两个对象进行比较 用法
: is(map1,map2)
简介
: 和js中对象的比较不同,在js中比较两个对象比较的是地址,但是在Immutable中比较的是这个对象hashCode和valueOf,只要两个对象的hashCode相等,值就是相同的,避免了深度遍历,提高了性能
在react中使用
react中通常使用purecomponent进行props的浅比较,从而控制shouldComponentUpdate的返回值
但是当传入prop或者state不止一层,或者传入的是Array和Object类型时,浅比较就失效了,当然也可以在shouldComponentUpdate中使用deepCopy和deepCompare来避免不必要的render,但是深拷贝和深比较都是非常消耗性能的,此时可以用Immutable来进行优化
Immutable提供了简洁高效的判断数据是否变化的方法,只需===和is就能比较是否需要执行render,而这个操作几乎是零成本的,所以可以极大提高性能
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
| import React, { Component } from 'react'; import ReactDOM from 'react-dom'; import { is, Map } from 'immutable';
class Caculator extends Component { state = { counter: Map({ number: 0}) }
handleClick = () => { let amount = this.amount.value ? Number(this.amount.value): 0; let counter = this.state.counter.update('number', val => val + amount); this.setState({counter}); }
shouldComponentUpdate(nextProps={},nextState={}{ if(Object.keys[this.state].length !== Object.keys(nextState).length){ return true; } for ( const key in nextState ) { if( !is(this.state[key], nextState[key])) { return true; } } return false }) render() { return ( <div> <p>{ this.state.counter.get('number')}</p> <input ref={input => this.amout = input} /> <button onClick="this.handleClick">+</button> </div> ) } } ReactDOM.render( <Caculator/>, document.getElementById('root') )
|
在redux中使用
可以使用redux-immutable中间件的方式实现redux与immutable搭配使用
建议把整个Redux的state树作为Immutable对象
注意
不要混合普通的JS对象和Immutable对象
把整个Redux的state树作为Immutable对象
除了展示组件,其他大部分组件都可以使用immutable对象提高效率
少用toJS方法,这个方法非常耗费性能,它会深度遍历数据转换成JS对象
你的Selector应该永远返回immutable对象
Immer.js
Immer 是 mobx 的作者写的一个 immutable 库,核心实现是利用 ES6 的 proxy,几乎以最小的成本实现了 js 的不可变数据结构,简单易用、体量小巧、设计巧妙,满足了我们对JS不可变数据结构的需求。
核心概念:
- currentState: 被操作对象的最初状态
- draftState: 根据 currentState 生成的草稿状态,它是 currentState 的代理,对 draftState 所做的任何修改都将被记录并用于生成 nextState 。在此过程中,currentState 将不受影响
- nextState:根据 draftState 生成的最终状态
- produce: 生产,用来生成 nextState 或 producer 的函数
- producer :生产者,通过 produce 生成,用来生产 nextState ,每次执行相同的操作
- recipe: 生产机器,用来操作 draftState 的函数
使用
1 2 3 4 5 6 7 8 9 10 11 12
| import { produce } from 'immer'
let nextState = produce(currentState, (draft) => {
})
let producer = produce((draft) => { draft.x = 2 }); let nextState = producer(currentState);
currentState === nextState;
|
patch布丁功能
通过此功能,可以方便进行详细的代码调试和跟踪,可以知道 recipe 内的做的每次修改,还可以实现时间旅行
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
| import produce, { applyPatches } from "immer"
let state = { x: 1 }
let replaces = []; let inverseReplaces = [];
state = produce( state, draft => { draft.x = 2; draft.y = 2; }, (patches, inversePatches) => { replaces = patches.filter(patch => patch.op === 'replace'); inverseReplaces = inversePatches.filter(patch => patch.op === 'replace'); } )
state = produce(state, draft => { draft.x = 3; }) console.log('state1', state);
state = applyPatches(state, replaces); console.log('state2', state);
state = produce(state, draft => { draft.x = 4; }) console.log('state3', state);
state = applyPatches(state, inverseReplaces); console.log('state4', state);
|
use-immer
immer.js的hook写法
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
| import React, { useCallback } from "react"; import { useImmer } from "use-immer";
const TodoList = () => { const [todos, setTodos] = useImmer([ { id: "React", title: "Learn React", done: true }, { id: "Immer", title: "Try Immer", done: false } ]);
const handleToggle = useCallback((id) => { setTodos((draft) => { const todo = draft.find((todo) => todo.id === id); todo.done = !todo.done; }); }, []);
const handleAdd = useCallback(() => { setTodos((draft) => { draft.push({ id: "todo_" + Math.random(), title: "A new todo", done: false }); }); }, []);
|
useImmerReducer
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
| import React, { useCallback } from "react"; import { useImmerReducer } from "use-immer";
const TodoList = () => { const [todos, dispatch] = useImmerReducer( (draft, action) => { switch (action.type) { case "toggle": const todo = draft.find((todo) => todo.id === action.id); todo.done = !todo.done; break; case "add": draft.push({ id: action.id, title: "A new todo", done: false }); break; default: break; } }, [ ] ); }
|
https://immerjs.github.io/immer/update-patterns
rxjs
rxjs是一个库,它通过使用observable序列来编写异步和基于事件的程序。它提供了核心类型Observable,附属类型(observer、schedulers、subjects)和类似于数组的操作符(map、filter、reduce、every)等,这些操作符可以把异步事件作为集合来处理
可以把rxjs当作用来处理事件的lodash
rxjs中的基本概念:
Observable(是一个可观察对象):表示一个概念,这个概念是一个可调用的未来值或事件的集合
Observer(观察者):一个回调函数的集合,它指定如何监听由Observable提供的值
Subscription(订阅):表示Observable的执行,它主要用于取消Obervable的执行
Operator(操作符):
Subject(主体):
Scheduler(调度器):
安装
通过npm安装
通过es6或者commonjs导入
1 2 3 4
| var Rx = require('rxjs/Rx') import Rx from 'rxjs/Rx'
Rx.observable.of(1,2,3)
|
按需导入函数(可以减少打包体积)
1 2 3 4 5 6 7 8 9
| import { Observable } from 'rxjs/observable' import 'rxjs/add/observable/of'; import 'rxjs/add/operator/map'
var Observable = require('rxjs/Observable').Observable require('rxjs/add/observable/of') require('rxjs/add/operator/map')
Observable.of(1,2,3).map(x => x+ '!!!');
|
注册事件
常规写法
1 2
| var button = document.querySelector('button') button.addEventListener('click',()=> console.log('click'))
|
rxjs写法
1 2 3
| var button = document.querySelector('button') Rx.observable.fromEvent(button,'click') .subscribe(()=> console.log('click'))
|
操作变量
常规写法是非纯函数,状态管理较乱
1 2 3
| var count = 0; var button = document.querySelector('button') button.addEventListener('click',()=> console.log(`click ${{++count}}`))
|
Rxjs将应用状态隔离起来
1 2 3 4
| var button = document.querySelector('button') Rx.observable.fromEvent(button,'click') .scan(count => count +1,0) .subscribe(count => console.log(`click ${count}`))
|
其他对变量的操作函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| var input = Rx.Observable.fromEvent(document.querySelector('input'),'input')
input.plunk('target','value').pairwise() .subsribe(value => console.log(value))
input.plunk('data').distinct() .subsribe(value => console.log(value))
input.plunk('data').
|
观察者模式与迭代器模式
Rxjs中包含两个基本概念:Observable和Observer
Observable作为被观察者,是一个可调用的未来值或事件的集合,支持异步或者同步数据流
Observer作为观察者,是一个回调函数的集合,他知道如何去监听由Observable提供的值
Observer与Observable之间是观察者模式,Observer通过Observable提供的subscribe方法订阅,Observable通过Observer提供的next方法向Observer发布事件
在Rxjs中,Observer除了有next方法来接收Observable的事件外,还提供了另外的两个方法:error方法和complete方法,来完成异常和完成状态,这个就是迭代器模式,类似于ES6中的Iterator遍历器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { Observable } from 'rxjs'
const observer = { next: (value) => console.log(value); error: err => console.error('Observer got an error' + err); complete: () => console.log('Observer got a complete notification') }
const observable = new Observable (function(observer) { observer.next('a'); observer.next('b'); observer.complete(); observer.next('c') })
const subscription = observable.subscribe(observer)
|
react使用
在react中,在componentDidMount生命周期中订阅observable,在componentWillUnmount中取消订阅
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
| import messages from './someObservable'
class Mycomponent extends ObservableComponent{ constructor(props){ super(props); this.state = {message:[]}; } componentDidMount(){ this.messages = messages .scan(messages,messages) => [messages].concat(messages,[]) .subscribe(messages => this.setState({messages:messages})) } componentWillUnmount(){ this.messages.unsubscribe(); } render() { return ( <div> <ul> {this.state.messages.map(message => <li>{message.text}</li>)} </ul> </div> ); } } export default MyComponent;
|
Observables与promise
单值与多值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| const numberPromise = new Promise((resolve) => { resolve(5); resolve(10) });
numberPromise.then(value => console.log(value));
const Observable = require('rxjs/Observable').Observable;
const numberObservable = new Observable((observer) => { observer.next(5); observer.next(10); });
numberObservable.subscribe(value => console.log(value));
|
执行机制:promise是立即执行,observable有subscribe才执行
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const promise = new Promise((resolve) => { console.log('promise call') resolve(1); console.log('promise end') })
const observable = new Observable(() => { console.log('I was called!'); });
|
promise不可取消,observables可取消
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const Observable = require('rxjs/Observable').Observable;
const observable = new Observable((observer) => { let i = 0; const token = setInterval(() => { observer.next(i++); }, 1000); return () => clearInterval(token); });
const subscription = observable.subscribe(value => console.log(value + '!'));
setTimeout(() => { subscription.unsubscribe(); }, 5000)
|
observables可以被多次执行,promise 是比较激进的,在一个promise被创建的时候,他就已经执行了,并且不能重复的被执行了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| let time; const waitOneSecondPromise = new Promise((resolve) => { console.log('promise call') time = new Date().getTime(); setTimeout(() => resolve('hello world'), 1000); });
waitOneSecondPromise.then((value) => {console.log( '第一次', value, new Date().getTime() - time)});
setTimeout(() => { waitOneSecondPromise.then((value) => {console.log('第二次', value, new Date().getTime() - time)}); }, 5000)
第一次 hello world 1007 第二次 hello world 5006
|
observable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| const Observable = require('rxjs/Observable').Observable;
let time; const waitOneSecondObservable = new Observable((observer) => { console.log('I was called'); time = new Date().getTime(); setTimeout(() => observer.next('hey girl'), 1000); });
waitOneSecondObservable.subscribe((value) => {console.log( '第一次', value, new Date().getTime() - time)});
setTimeout(() => { waitOneSecondObservable.subscribe((value) => {console.log( '第二次', value, new Date().getTime() - time)}); }, 5000)
I was called 第一次 hey girl 1003 I was called 第二次 hey girl 1003
|
用observable已经可以实现多次订阅,但是这有时候可能不能符合我们的业务场景,在http请求中,我们可能希望只发一次请求,但是结果被多个订阅者共用。 Observables 本身没有提供这个功能,我们可以用 RxJS 这个库来实现,它有一个 share 的 operator
1 2 3 4 5 6 7 8 9 10 11 12
| const waitOneSecondObservable = new Observable((observer) => { });
const sharedWaitOneSecondObservable = waitOneSecondObservable.share();
sharedWaitOneSecondObservable.subscribe(doSomething);
sharedWaitOneSecondObservable.subscribe(doSomethingElse);
|
promise是异步函数,而observable可以根据需求是否使用异步
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
| const promise = new Promise((resolve) => { resolve(5); });
promise.then(value => console.log(value + '!'));
console.log('And now we are here.');
And now we are here. 5! const Observable = require('rxjs/Observable').Observable;
const observable = new Observable((observer) => { setTimeout(() => { observer.next(5); }) });
observable.subscribe(value => console.log(value + '!')); console.log('And now we are here.');
这个如果是直接next 5,则输出是 5! -> And now we are here. 采用setTimeout next 5, 则相反 And now we are here.-> 5!
|
rxjs中有一些操作符可以让监听强制为异步的方式,例如 observeOn。
https://www.jianshu.com/p/273e7ab02fa1
cyclejs
测试框架
Remix.js
Remix由 React Router 原班团队打造,基于 TypeScript 与 React,内建 React Router V6 特性的全栈 Web 框架 Remix 正式开源。
Remix 开源之后可以说是在 React 全栈框架领域激起千层浪,绝对可以算是 Next.js 的强劲对手。Remix 的特性如下:
- 追求速度,然后是用户体验(UX),支持任何 SSR/SSG 等
- 基于 Web 基础技术,如 HTML/CSS 与 HTTP 以及 Web Fecth API,在绝大部分情况可以不依赖于 JavaScript 运行,所以可以运行在任何环境下,如 Web Browser、Cloudflare Workers、Serverless 或者 Node.js 等
- 客户端与服务端一致的开发体验,客户端代码与服务端代码写在一个文件里,无缝进行数据交互,同时基于 TypeScript,类型定义可以跨客户端与服务端共用
- 内建文件即路由、动态路由、嵌套路由、资源路由等
- 干掉 Loading、骨架屏等任何加载状态,页面中所有资源都可以预加载(Prefetch),页面几乎可以立即加载
- 告别以往瀑布式(Waterfall)的数据获取方式,数据获取在服务端并行(Parallel)获取,生成完整 HTML 文档,类似 React 的并发特性
- 提供开发网页需要所有状态,开箱即用;提供所有需要使用的组件,包括 、、 、