React(六)

React(六)

​ 基于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 //999

改变了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; // true

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); // { x: 3, y: 2 }

state = applyPatches(state, replaces);
console.log('state2', state); // { x: 2, y: 2 }

state = produce(state, draft => {
draft.x = 4;
})
console.log('state3', state); // { x: 4, y: 2 }

state = applyPatches(state, inverseReplaces);
console.log('state4', state); // { x: 1, y: 2 }

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;
}
},
[ /* initial todos */ ]
);
}

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安装

1
npm install rxjs

通过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)); //. 5

const Observable = require('rxjs/Observable').Observable;
// observables的写法,使用 next 替代 promise 的 resolve, 用subscribe 取代then来订阅结果。
const numberObservable = new Observable((observer) => {
observer.next(5);
observer.next(10);
});

numberObservable.subscribe(value => console.log(value));

// 输出 5 10

执行机制: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')
})

// 执行这段代码 promise call 和 promise end 会立即执行
const observable = new Observable(() => {
console.log('I was called!');
});

// 此时并没有console

// 只有 observable.subscribe(); 这个时候 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)

// 结果
// 0!
// 1!
// 2!
// 3!

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)

// 输出结果是 promise call
第一次 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) => {
// 发送http请求
});

const sharedWaitOneSecondObservable =
waitOneSecondObservable.share();

sharedWaitOneSecondObservable.subscribe(doSomething);

sharedWaitOneSecondObservable.subscribe(doSomethingElse);

// 使用了share,虽然subscribe了多次,但是仅发送一次请求,share了结果。

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) => {
// observer.next(5);
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 的并发特性
  • 提供开发网页需要所有状态,开箱即用;提供所有需要使用的组件,包括

评论

You forgot to set the app_id or app_key for Valine. Please set it in _config.yml.

 

本文章阅读量:

  0

IT学徒、技术民工、斜杠青年

机器人爱好者、摄影爱好者

PS、PR、LR、达芬奇潜在学习者

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×