在 2017 年学习 React + Redux 的一些建议(中篇)

2017/09/11 · JavaScript
· React,
Redux

原文出处: 郭永峰   

你需要 Mobx 还是 Redux ?

2018/02/22 · JavaScript
· mobx,
Redux

原文出处:
熊建刚的博客   

在过去一年,越来越多的项目继续或者开始使用React和Redux开发,这是目前前端业内很普遍的一种前端项目解决方案,但是随着开发项目越来越多,越来越多样化时,个人又有了不同的感受和想法。是不是因为已经有了一个比较普遍的,熟悉的项目技术栈,我们就一直完全沿用呢,有没有比他更适合的方案呢?恰逢团队最近有一个新项目,于是博主开始思考,有没有可能使用其他可替代技术开发呢?既能提高开发效率,又能拓展技术储备和眼界,经过调研,选择了Mobx,最终使用React+Mobx搭建了新项目,本篇总结分享从技术选型到项目实现的较完整过程,希望共同进步。

索引

  • 1 前言
  • 2 状态管理
  • 3 Mobx VS Redux
    • 3.1 Redux
    • 3.2 Mobx
    • 3.3 函数式和面向对象
    • 3.4
      单一store和多store
    • 3.5
      JavaScript对象和可观察对象
    • 3.6
      不可变(Immutable)和可变(Mutable)
    • 3.7
      mobx-react和react-redux
  • 4 选择Mobx的原因
  • 5 不选择Mobx的可能原因
  • 6 代码对比
    • 6.1 架构
    • 6.2 注入Props
    • 6.3
      定义Action/Reducer等
    • 6.4 异步Action
  • 7 一些想法
  • 8 参考

webgl 性能优化初尝

2017/05/14 · HTML5 ·
WebGL

原文出处:
AlloyTeam   

上次文章介绍了如何用webgl快速创建一个自己的小世界,在我们入门webgl之后,并且可以用原生webgl写demo越来越复杂之后,大家可能会纠结一点:就是我使用webgl的姿势对不对。因为webgl可以操控shader加上超底层API,带来了一个现象就是同样一个东西,可以有多种的实现方式,而此时我们该如何选择呢?这篇文章将稍微深入一点webgl,给大家介绍一点webgl的优化知识。

讲webgl优化之前我们先简单回忆一下canvas2D的优化,常用的display
list、动态区域重绘等等。用canvas2D多的同学应该对以上的优化或多或少都有了解,但是你对webgl的优化了解么,如果不了解的话往下看就对了~这里会先从底层图像是如何渲染到屏幕上开始,逐步开始我们的webgl优化。

图片 1对于学习 Redux 的一些建议

React 和 Redux 经常结合在一起使用,Redux 是 flux
架构模式的一种优秀实现,并且在 React 社区被广泛使用,但也不是完全和
React 耦合在一起的。

前言

当我们使用React开发web应用程序时,在React组件内,可以使用this.setState()this.state处理或访问组件内状态,但是随着项目变大,状态变复杂,通常需要考虑组件间通信问题,主要包括以下两点:

  1. 某一个状态需要在多个组件间共享(访问,更新);
  2. 某组件内交互需要触发其他组件的状态更新;

关于这些问题,React组件开发实践推荐将公用组件状态提升:

Often, several components need to reflect the same changing data. We
recommend lifting the shared state up to their closest common ancestor

通常多组件需要处理同一状态,我们推荐将共享状态提升至他们的共同最近祖先组件内。更多详情查看

当项目越发复杂时,我们发现仅仅是提升状态已经无法适应如此复杂的状态管理了,程序状态变得比较难同步,操作,到处是回调,发布,订阅,这意味着我们需要更好的状态管理方式,于是就引入了状态管理库,如Redux,Mobx,Jumpsuit,Alt.js等。

gpu如何渲染出一个物体

先看一个简单的球的例子,下面是用webgl画出来的一个球,加上了一点光的效果,代码很简单,这里就不展开说了。
一个球
这个球是一个简单的3D模型,也没有复杂的一些变化,所以例子中的球性能很好,看FPS值稳定在60。后面我们会尝试让它变得复杂起来,然后进行一些优化,不过这一节我们得先了解渲染的原理,知其根本才能知道优化的原理。

我们都知道webgl与着色器是密不可分的关系,webgl当中有顶点着色器和片段着色器,下面用一张图来简单说明下一个物体由0到1生成的过程。
图片 2
0就是起点,对应图上面的3D mesh,在程序中这个就是3D顶点信息
1就是终点,对应图上面的Image Output,此时已经渲染到屏幕上了
我们重点是关注中间那三个阶段,第一个是一个标准的三角形,甚至三角形上面用三个圈指明了三个点,再加上vertex关键字,可以很明白的知道是顶点着色器处理的阶段,图翻译为大白话就是:
我们将顶点信息传给顶点着色器(drawElements/drawArray),然后着色器将顶点信息处理并开始画出三角形(gl_Position)

然后再看后两个图,很明显的fragments关键字指明了这是片元着色器阶段。Rasterization是光栅化,从图上直观的看就是三角形用三条线表示变成了用像素表示,其实实际上也是如此,更详细的可以看下面地址,这里不进行展开。
如何理解光栅化-知乎
后面阶段是上色,可以用textture或者color都可以,反正统一以rgba的形式赋给gl_FragColor
图中vertexShader会执行3次,而fragmentShader会执行35次(有35个方块)
发现fragmentShader执行次数远远超过vertexShader,此时机智的朋友们肯定就想到尽可能的将fragmentShader中的计算放在vertexShader中,但是能这样玩么?

强行去找还是能找到这样的场景的,比如说反射光。反射光的计算其实不是很复杂,但也稍微有一定的计算量,看核心代码

JavaScript

vec3 L = normalize(uLightDirection); vec3 N = normalize(vNormal); float
lambertTerm = dot(N, -L); vIs = vec4(0.0, 0.0, 0.0, 1.0); if
(lambertTerm > 0.0) { vec3 E = normalize(vEye); vec3 R = reflect(L,
N); float specular = pow(max(dot(R, E), 0.0), uShininess); vIs =
uLightSpecular * uMaterialSpecular * specular; }

1
2
3
4
5
6
7
8
9
10
11
vec3 L = normalize(uLightDirection);
vec3 N = normalize(vNormal);
float lambertTerm = dot(N, -L);
vIs = vec4(0.0, 0.0, 0.0, 1.0);
if (lambertTerm > 0.0) {
    vec3 E = normalize(vEye);
    vec3 R = reflect(L, N);
    float specular = pow(max(dot(R, E), 0.0), uShininess);
    vIs = uLightSpecular * uMaterialSpecular * specular;
}

上面反射光代码就不细说了,核心就是内置的reflect方法。这段代码既可以放在fragmentShader中也可以放在vertexShader中,但是二者的结果有些不同,结果分别如下
放在vertexShader中
放在fragmentShader中

所以说这里的优化是有缺陷的,可以看到vertexShader中执行光计算和fragmentShader中执行生成的结果区别还是蛮大的。换言之如果想要实现真实反射光的效果,必须在fragmentShader中去计算。开头就说了这篇文章的主题在同样的一个效果,用什么方式是最优的,所以continue~

全局 state

并不是所有的全局state都需要被存储起来,一些组件可以使用 setState
来管理组件的内部状态,这也是为什么在学习 Redux 前要掌握 React 中的
setState ,否则你将习惯式的把所有的global
state都存储在store里面。所以思考一下,在大型开发团队里面开发的复杂应用,你更不能将应用的所有
state 都切换成全局状态。

状态管理

状态管理库,无论是Redux,还是Mobx这些,其本质都是为了解决状态管理混乱,无法有效同步的问题,它们都支持:

  1. 统一维护管理应用状态;
  2. 某一状态只有一个可信数据来源(通常命名为store,指状态容器);
  3. 操作更新状态方式统一,并且可控(通常以action方式提供更新状态的途径);
  4. 支持将store与React组件连接,如react-reduxmobx-react;通常使用状态管理库后,我们将React组件从业务上划分为两类:
    1. 容器组件(Container
      Components):负责处理具体业务和状态数据,将业务或状态处理函数传入展示型组件;
    2. 展示型组件(Presentation
      Components):负责展示视图,视图交互回调内调用传入的处理函数;

gpu计算能力很猛

上一节说了gpu渲染的原理,这里再随便说几个gpu相关的新闻
百度人工智能大规模采用gpu,PhysX碰撞检测使用gpu提速……种种类似的现象都表明了gpu在单纯的计算能力上是超过普通的cpu,而我们关注一下前一节shader里面的代码

vertexShader

JavaScript

void main() { vec4 vertex = uMMatrix * uRMatrix * vec4(aPosition,
1.0); vNormal = vec3(uNMMatrix * uNRMatrix * vec4(aNormal, 1.0)); vEye
= -vec3((uVMatrix * vertex).xyz); gl_Position = uPMatrix * uVMatrix
* vertex; }

1
2
3
4
5
6
7
void main() {
    vec4 vertex = uMMatrix * uRMatrix * vec4(aPosition, 1.0);
    vNormal = vec3(uNMMatrix * uNRMatrix * vec4(aNormal, 1.0));
    vEye = -vec3((uVMatrix * vertex).xyz);
    gl_Position = uPMatrix * uVMatrix * vertex;
}

fragmentShader

JavaScript

void main() { vec3 L = normalize(uLightDirection); vec3 N =
normalize(vNormal); float lambertTerm = dot(N, -L); vec4 Ia =
uLightAmbient * uMaterialAmbient; vec4 Id = vec4(0.0, 0.0, 0.0, 1.0);
vec4 Is = vec4(0.0, 0.0, 0.0, 1.0); if (lambertTerm > 0.0) { Id =
uLightDiffuse * uMaterialDiffuse * lambertTerm; vec3 E =
normalize(vEye); vec3 R = reflect(L, N); float specular = pow(max(dot(R,
E), 0.0), uShininess); Is = uLightSpecular * uMaterialSpecular *
specular; } vec4 finalColor = Ia + Id + Is; finalColor.a = 1.0;
gl_FragColor = finalColor; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void main() {
    vec3 L = normalize(uLightDirection);
    vec3 N = normalize(vNormal);
    float lambertTerm = dot(N, -L);
    vec4 Ia = uLightAmbient * uMaterialAmbient;
    vec4 Id = vec4(0.0, 0.0, 0.0, 1.0);
    vec4 Is = vec4(0.0, 0.0, 0.0, 1.0);
    if (lambertTerm > 0.0) {
        Id = uLightDiffuse * uMaterialDiffuse * lambertTerm;
        vec3 E = normalize(vEye);
        vec3 R = reflect(L, N);
        float specular = pow(max(dot(R, E), 0.0), uShininess);
        Is = uLightSpecular * uMaterialSpecular * specular;
    }
    vec4 finalColor = Ia + Id + Is;
    finalColor.a = 1.0;
    gl_FragColor = finalColor;
}

可以发现逻辑语句很少,更多的都是计算,特别是矩阵的运算,两个mat4相乘通过js需要写成这样(代码来自glMatrix)

JavaScript

mat4.multiply = function (mat, mat2, dest) { if (!dest) { dest = mat }
// Cache the matrix values (makes for huge speed increases!) var a00 =
mat[0], a01 = mat[1], a02 = mat[2], a03 = mat[3]; var a10 =
mat[4], a11 = mat[5], a12 = mat[6], a13 = mat[7]; var a20 =
mat[8], a21 = mat[9], a22 = mat[10], a23 = mat[11]; var a30 =
mat[12], a31 = mat[13], a32 = mat[14], a33 = mat[15]; var b00 =
mat2[0], b01 = mat2[1], b02 = mat2[2], b03 = mat2[3]; var b10 =
mat2[4], b11 = mat2[5], b12 = mat2[6], b13 = mat2[7]; var b20 =
mat2[8], b21 = mat2[9], b22 = mat2[10], b23 = mat2[11]; var b30
= mat2[12], b31 = mat2[13], b32 = mat2[14], b33 = mat2[15];
dest[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30; dest[1]
= b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31; dest[2] = b00 *
a02 + b01 * a12 + b02 * a22 + b03 * a32; dest[3] = b00 * a03 + b01
* a13 + b02 * a23 + b03 * a33; dest[4] = b10 * a00 + b11 * a10 +
b12 * a20 + b13 * a30; dest[5] = b10 * a01 + b11 * a11 + b12 *
a21 + b13 * a31; dest[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13
* a32; dest[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
dest[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30; dest[9]
= b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31; dest[10] = b20 *
a02 + b21 * a12 + b22 * a22 + b23 * a32; dest[11] = b20 * a03 +
b21 * a13 + b22 * a23 + b23 * a33; dest[12] = b30 * a00 + b31 *
a10 + b32 * a20 + b33 * a30; dest[13] = b30 * a01 + b31 * a11 +
b32 * a21 + b33 * a31; dest[14] = b30 * a02 + b31 * a12 + b32 *
a22 + b33 * a32; dest[15] = b30 * a03 + b31 * a13 + b32 * a23 +
b33 * a33; return dest; };

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
mat4.multiply = function (mat, mat2, dest) {
    if (!dest) {
        dest = mat
    }
    // Cache the matrix values (makes for huge speed increases!)
    var a00 = mat[0],
        a01 = mat[1],
        a02 = mat[2],
        a03 = mat[3];
    var a10 = mat[4],
        a11 = mat[5],
        a12 = mat[6],
        a13 = mat[7];
    var a20 = mat[8],
        a21 = mat[9],
        a22 = mat[10],
        a23 = mat[11];
    var a30 = mat[12],
        a31 = mat[13],
        a32 = mat[14],
        a33 = mat[15];
    var b00 = mat2[0],
        b01 = mat2[1],
        b02 = mat2[2],
        b03 = mat2[3];
    var b10 = mat2[4],
        b11 = mat2[5],
        b12 = mat2[6],
        b13 = mat2[7];
    var b20 = mat2[8],
        b21 = mat2[9],
        b22 = mat2[10],
        b23 = mat2[11];
    var b30 = mat2[12],
        b31 = mat2[13],
        b32 = mat2[14],
        b33 = mat2[15];
    dest[0] = b00 * a00 + b01 * a10 + b02 * a20 + b03 * a30;
    dest[1] = b00 * a01 + b01 * a11 + b02 * a21 + b03 * a31;
    dest[2] = b00 * a02 + b01 * a12 + b02 * a22 + b03 * a32;
    dest[3] = b00 * a03 + b01 * a13 + b02 * a23 + b03 * a33;
    dest[4] = b10 * a00 + b11 * a10 + b12 * a20 + b13 * a30;
    dest[5] = b10 * a01 + b11 * a11 + b12 * a21 + b13 * a31;
    dest[6] = b10 * a02 + b11 * a12 + b12 * a22 + b13 * a32;
    dest[7] = b10 * a03 + b11 * a13 + b12 * a23 + b13 * a33;
    dest[8] = b20 * a00 + b21 * a10 + b22 * a20 + b23 * a30;
    dest[9] = b20 * a01 + b21 * a11 + b22 * a21 + b23 * a31;
    dest[10] = b20 * a02 + b21 * a12 + b22 * a22 + b23 * a32;
    dest[11] = b20 * a03 + b21 * a13 + b22 * a23 + b23 * a33;
    dest[12] = b30 * a00 + b31 * a10 + b32 * a20 + b33 * a30;
    dest[13] = b30 * a01 + b31 * a11 + b32 * a21 + b33 * a31;
    dest[14] = b30 * a02 + b31 * a12 + b32 * a22 + b33 * a32;
    dest[15] = b30 * a03 + b31 * a13 + b32 * a23 + b33 * a33;
    return dest;
};

可以说相比普通的加减乘除来说矩阵相关的计算量还是有点大的,而gpu对矩阵的计算有过专门的优化,是非常快的

所以我们第一反应肯定就是能在shader中干的活就不要让js折腾啦,比如说前面代码中将proMatrix/viewMatrix/modelMatrix都放在shader中去计算。甚至将modelMatrix里面再区分成moveMatrix和rotateMatrix可以更好的去维护不是么~

但是了解threejs或者看其他学习资料的的同学肯定知道threejs会把这些计算放在js中去执行,这是为啥呢??比如下方代码(节选自webgl编程指南)

vertexShader中

JavaScript

…… attribute vec4 u_MvpMatrix;…… void main() { gl_Position =
u_MvpMatrix * a_Position; } ……

1
2
3
4
5
6
……
attribute vec4 u_MvpMatrix;……
void main() {
    gl_Position = u_MvpMatrix * a_Position;
}
……

javascript中

JavaScript

…… var mvpMatrix = new Matrix4(); mvpMatrix.setPerspective(30,
canvas.width / canvas.height, 1, 100); mvpMatrix.lookAt(3, 3, 7, 0, 0,
0, 0, 1, 0); gl.uniformMatrix4fv(u_MvpMatrix, false,
mvpMatrix.elements); ……

1
2
3
4
5
6
……
var mvpMatrix = new Matrix4();
mvpMatrix.setPerspective(30, canvas.width / canvas.height, 1, 100);
mvpMatrix.lookAt(3, 3, 7, 0, 0, 0, 0, 1, 0);
gl.uniformMatrix4fv(u_MvpMatrix, false, mvpMatrix.elements);
……

这里居然把proMatrix/viewMatrix/modelMatrix全部在js中计算好,然后传入到shader中去,为什么要这样呢?

结合第一节我们看下vertexShader执行的次数是和顶点有关系的,而每个顶点都需要做对象坐标->世界坐标->眼睛坐标的变换,如果传入三个顶点,就代表gpu需要将proMatrix
* viewMatrix *
modelMatrix计算三次,而如果我们在js中就计算好,当作一个矩阵传给gpu,则是极好的。js中虽然计算起来相较gpu慢,但是胜在次数少啊。
看下面两个结果

在shader中计算
在js中计算

第一个是将矩阵都传入给gpu去计算的,我这边看到FPS维持在50左右
第二个是将部分矩阵计算在js中完成的,我这边看到FPS维持在60样的
这里用的180个球,如果球的数量更大,区别还可以更加明显。所以说gpu计算虽好,但不要滥用呦~

项目目录如何组织

这篇文章organizing-redux-application
给出了三种建议方式来组织项目结构。

第一种方式是按功能划分

React + Redux 的一些教程经常给我们展示按功能划分的目录,这也是一种很好的
React + Redux 学习方式,不过,将应用的所有 reducers 和 actions
都放在专门的文件夹维护的方案,并不是所有人都能赞同。

JavaScript

src/ –actions/ –reducers/ –components/

1
2
3
4
src/
–actions/
–reducers/
–components/

经常听到的有建设性的想法是,目录划分应该以组件为核心,每个目录应该有组件本身以及它所对应的
reducers、actions,那么一个示例的目录结构应该是这样的:

JavaScript

message/ –components –reducer.js –actions.js

1
2
3
4
message/
–components
–reducer.js
–actions.js

一个包含 container component 、presenter
component以及测试相关的详细的组件目录会是这样的:

JavaScript

message/ –components/ —-messageItem/ ——presenter.js ——spec.js
—-messageList/ ——container.js ——presenter.js ——spec.js
–reducer/ —-index.js —-spec.js –actions/ —-index.js —-spec.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
message/
–components/
—-messageItem/
——presenter.js
——spec.js
—-messageList/
——container.js
——presenter.js
——spec.js
–reducer/
—-index.js
—-spec.js
–actions/
—-index.js
—-spec.js

当然了,也并不是大家都会喜欢这种方式。(其实,我个人是很赞同这样的就近维护组件的原则的,因为将各个功能性的reducer和action都丢到对应的目录,这以后维护起来会更加困难,文件也不好找,这可不像是MVC那样的分层结构。)尤其是将reducer隐藏在各个功能目录中,这也不利于全局性的来理解使用
redux 的架构意图。所以建议是适当的在最初就抽取一些 reducers
来共享他们所包含的功能。

但在现实场景中,尤其是多个团队在同一个应用项目中协作的时候,在开发进度的压力之下,并没有那么多机会来正确的抽象出一些
reducers。反而通常是一口气的封装所有的功能模块,只为了感觉把活给干完了,让需求按时上线。

第二种方式是对功能模块划分清晰的界限

给每个模块都设置一个 index.js
文件作为入口,这个文件只是用于导出一些API给其他的模块使用。在基于 React

  • Redux 的应用中,index.js 文件可以用于导出一个 container components
    ,或是一个presenter components、action creators、能用于其他地方的
    reducer(但不是最终的reducer)。那么,基于这样的思考,我们的目录就可以变成这样了:

JavaScript

message/ –index.js –components/ —-messageItem/ ——index.js
——presenter.js ——spec.js —-messageList/ ——index.js
——container.js ——presenter.js ——spec.js –reducer/
—-index.js —-spec.js –actions/ —-index.js —-spec.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
message/
–index.js
–components/
—-messageItem/
——index.js
——presenter.js
——spec.js
—-messageList/
——index.js
——container.js
——presenter.js
——spec.js
–reducer/
—-index.js
—-spec.js
–actions/
—-index.js
—-spec.js

那么,在当前功能模块下的 index.js 文件应该包含这些代码:

JavaScript

import MessageList from ‘./messageList’; export default MessageList;
export MessageItem from ‘./messageItem’; export reducer from
‘./reducer’; export actions from ‘./actions’;

1
2
3
4
5
6
7
import MessageList from ‘./messageList’;
 
export default MessageList;
 
export MessageItem from ‘./messageItem’;
export reducer from ‘./reducer’;
export actions from ‘./actions’;

好了,这样外部的其他模块就可以这样在他的 index.js 文件中调用 message
模块了。

JavaScript

// bad import { reducer } from ./message/reducer; // good import {
reducer } from ./message;

1
2
3
4
5
// bad
import { reducer } from ./message/reducer;
 
// good
import { reducer } from ./message;

收获:按功能模块以及清晰的界限可以帮助我们很好的组织代码和目录。

Mobx VS Redux

目前来看,Redux已是React应用状态管理库中的霸主了,而Mobx则是一方诸侯,我们为什么要选择Mobx,而不是继续沿用Redux呢,那就需要比较他们的异同了。

Mobx和Redux都是JavaScript应用状态管理库,都适用于React,Angular,VueJs等框架或库,而不是局限于某一特定UI库。

js与shader交互的成本

动画就是画一个静态场景然后擦掉接着画一个新的,重复不断。第一节中我们用的是setInterval去执行的,每一个tick中我们必须的操作就是更新shader中的attribute或者uniform,这些操作是很耗时的,因为是js和glsl程序去沟通,此时我们想一想,有没有什么可以优化的地方呢?
比如有一个场景,同样是一个球,这个球的材质颜色比较特殊
图片 3
x,y方向上都有着渐变,不再是第一节上面一个色的了,此时我们该怎么办?
首先分析一下这个这个球
图片 4
总而言之就是水平和垂直方向都有渐变,如果按之前的逻辑扩展,就意味着我们得有多个uniform去标识
我们先尝试一下,用如下的代码,切换uniform的方式

JavaScript

…… var colorArr = []; var temp; for (i = 1; i <= granularity; i++)
{ temp = 0.8 – (i / granularity * 0.7); for (j = 1; j <=
granularity; j++) { colorArr.push([0.8 – (j / granularity * 0.7),
temp, 0.1, 1.0]); } } …… for (i = 0; i < granularity; i++) { for (j
= 0; j < granularity; j++) { webgl.uniform4fv(uMaterialDiffuse,
colorArr[i * granularity + j]); webgl.drawElements(webgl.TRIANGLES,
6, webgl.UNSIGNED_SHORT, (i * granularity * 6 + j * 6) * 2); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
……
var colorArr = [];
var temp;
for (i = 1; i <= granularity; i++) {
    temp = 0.8 – (i / granularity * 0.7);
    for (j = 1; j <= granularity; j++) {
        colorArr.push([0.8 – (j / granularity * 0.7), temp, 0.1, 1.0]);
    }
}
……
for (i = 0; i < granularity; i++) {
    for (j = 0; j < granularity; j++) {
        webgl.uniform4fv(uMaterialDiffuse, colorArr[i * granularity + j]);
        webgl.drawElements(webgl.TRIANGLES, 6, webgl.UNSIGNED_SHORT, (i * granularity * 6 + j * 6) * 2);
    }
}

使用切换uniform的方式

发现FPS在40左右,还是蛮卡的。然后我们考虑一下,卡顿在哪?
vertexShader和fragmentShader执行的次数可以说都是一样的,但是uniform4fv和drawElements每一次tick中执行了多次,就代表着js与shader耗费了较大的时间。那我们应该如何优化呢?

核心在避免多次改变uniform,比方说我们可以尝试用attribute去代替uniform
看下结果怎样

使用attribute的方式

瞬间FPS就上去了对不~所以说灵活变通很重要,不能一味的死板,尽可能的减少js与shader的交互对性能的提高是大大有帮助的~

命名约定

在软件编程中命名可真是一件令人头疼的事情,这跟给孩子取名一样费劲,哈哈。合适的命名是实现可维护性、易于理解的代码的最好实践,React

  • Redux
    的应用中提供了大量的约束来帮助我们组织代码,而且不会在命名上固执己见。无论你的函数封装在
    reducer 还是 component 中,在action creator 或是 selector
    中,你都应该有一个命名约束,并且在扩展应用之前就确定如何命名,否则经常会让我们陷入难以捉摸的回调和重构当中。

而我习惯为每个类型的函数都加上一个前缀。

  • 在组件的callback中,为每个函数都加上 on 作为前缀,比如 onCreateRplay
  • 在改变 state 的 reducer 中加上 applay 作为前缀,比如
    applyCreateReply
  • 在 selector 中 加上 get 作为前缀,比如 getReply
  • 在 action creator 中加上 do 作为前缀,比如 doCreateReply

也许你不一定习惯这种加上前缀的方式,不过我还是推荐给你,同时也建议找到自己喜欢的命名约束规则。

Redux

要介绍Redux,我们就不得不谈到Flux了:

Flux is the application architecture that Facebook uses for building
client-side web applications.It’s more of a pattern rather than a
formal framework

Flux是Facebook用来开发客户端-服务端web应用程序的应用架构,它更多是一种架构模式,而非一个特定框架。详解Flux。

而Redux更多的是遵循Flux模式的一种实现,是一个JavaScript库,它关注点主要是以下几方面:

  1. Action:一个JavaScript对象,描述动作相关信息,主要包含type属性和payload属性:
    1. type:action 类型;
    2. payload:负载数据;
  2. Reducer:定义应用状态如何响应不同动作(action),如何更新状态;
  3. Store:管理action和reducer及其关系的对象,主要提供以下功能:
    1. 维护应用状态并支持访问状态(getState());
    2. 支持监听action的分发,更新状态(dispatch(action));
    3. 支持订阅store的变更(subscribe(listener));
  4. 异步流:由于Redux所有对store状态的变更,都应该通过action触发,异步任务(通常都是业务或获取数据任务)也不例外,而为了不将业务或数据相关的任务混入React组件中,就需要使用其他框架配合管理异步任务流程,如redux-thunkredux-saga等;

切换program的成本

上一节我们发现频繁切换切换uniform的开销比较大,有没有更大的呢?
当然有,那就是切换program,我们把之前的例子用切换program的方式试下,直接看下面的例子

点击前慎重,可能会引起浏览器崩溃
切换program

已经不需要关心FPS的了,可以直观的感觉到奇卡无比。切换program的成本应该是在webgl中开销是非常大的了,所以一定要少切换program

这里说的是少切换program,而不是说不要切换program,从理论上来说可以单个program写完整个程序的呀,那什么时候又需要切换program呢?

program的作用是代替if else语句,相当于把if
else抽出来单独一个program,所以就是如果一个shader里面的if
else多到开销超过program的开销,此时我们就能选择用program啦。

当然这里的度有点难把握,需要开发者自己多尝试,结合实际情况进行选择。这里有一个关于选择program还是if
else的讨论,感兴趣的同学可以看看

追踪状态的改变

在持续迭代中的应用免不了定义大量的 action,而且还需要追溯 state
是如何改变的,redux-logger
可以帮助你看到所有的 state change。每条日志都会显示出 previous
state、执行的 action、next state。

不过你得确保 actions 是可被设备的,因此我建议为不同类型的 action
都加上一个前缀,比如这样:

JavaScript

const MESSAGE_CREATE_REPLY = ‘message/CREATE_REPLY’;

1
const MESSAGE_CREATE_REPLY = ‘message/CREATE_REPLY’;

这样的话,无论你在何时触发了信息回复这个动作,你都能看到
message/CREATE_REPLY 这一条日志,如果出现 state
异常,便能迅速查到是那条错误的 state 改变而导致的。

Mobx

Mobx是一个透明函数响应式编程(Transparently Functional Reactive
Programming,TFRP)的状态管理库,它使得状态管理简单可伸缩:

Anything that can be derived from the application state, should be
derived. Automatically.

任何起源于应用状态的数据应该自动获取。

其原理如图:

图片 5

  1. Action:定义改变状态的动作函数,包括如何变更状态;
  2. Store:集中管理模块状态(State)和动作(action);
  3. Derivation(衍生):从应用状态中派生而出,且没有任何其他影响的数据,我们称为derivation(衍生),衍生在以下情况下存在:
    1. 用户界面;
    2. 衍生数据;衍生主要有两种:
      1. Computed Values(计算值):计算值总是可以使用纯函数(pure
        function)从当前可观察状态中获取;
      2. Reactions(反应):反应指状态变更时需要自动发生的副作用,这种情况下,我们需要实现其读写操作;

import {observable, autorun} from ‘mobx’; var todoStore = observable({
/* some observable state */ todos: [], /* a derived value */ get
completedCount() { return this.todos.filter(todo =>
todo.completed).length; } }); /* a function that observes the state */
autorun(function() { console.log(“Completed %d of %d items”,
todoStore.completedCount, todoStore.todos.length ); }); /* ..and some
actions that modify the state */ todoStore.todos[0] = { title: “Take
a walk”, completed: false }; // -> synchronously prints: ‘Completed 0
of 1 items’ todoStore.todos[0].completed = true; // ->
synchronously prints: ‘Completed 1 of 1 items’

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
import {observable, autorun} from ‘mobx’;
 
var todoStore = observable({
    /* some observable state */
    todos: [],
 
    /* a derived value */
    get completedCount() {
        return this.todos.filter(todo => todo.completed).length;
    }
});
 
/* a function that observes the state */
autorun(function() {
    console.log("Completed %d of %d items",
        todoStore.completedCount,
        todoStore.todos.length
    );
});
 
/* ..and some actions that modify the state */
todoStore.todos[0] = {
    title: "Take a walk",
    completed: false
};
// -> synchronously prints: ‘Completed 0 of 1 items’
 
todoStore.todos[0].completed = true;
// -> synchronously prints: ‘Completed 1 of 1 items’

结语

我们这里从原理触发,尝试了webgl的一些优化~如果你有什么建议和疑惑~欢迎留言讨论~

1 赞 收藏
评论

图片 6

尽可能让 state tree 扁平化

Redux 中,扁平化的 state tree可以让你的 reducers
更加的简单,这样你就不需要在整个 store 的状态树中深层的查找到某个
state 后再将其修改,而是可以很轻松的就能实现。不过,在 Redux
中却不能做这么做,因为 state 是不可变的。

如果你正在开发一个博客应用,需要维护一个类似这样的列表对象,列表中包含
authorcomment 字段:

JavaScript

{ post: { author: {}, comments: [], } }

1
2
3
4
5
6
{
  post: {
    author: {},
    comments: [],
  }
}

不过实际情况是每个对象都需要有对应的 id 来进行维护:

JavaScript

{ post: { id: ‘1’, author: { id: ‘a’, … }, comments: [ { id: ‘z’, …
}, … ], } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
  post: {
    id: ‘1’,
    author: {
      id: ‘a’,
      …
    },
    comments: [
      {
        id: ‘z’,
        …
      },
      …
    ],
  }
}

这个时候,我们将数据序列化之后将会变得更有意义,数据解构变得更加扁平化了。序列化之后的数据通过
id 关联其他字段,之后,你就可以通过实体对象来将其报酬,通过 id
来进行关联数据的查找。

JavaScript

{ posts: { 1: { authorId: ‘a’, commentIds: [‘z’, …] } }, authors: {
a: { … } }, comments: { z: { … } }, }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  posts: {
    1: {
      authorId: ‘a’,
      commentIds: [‘z’, …]
    }
  },
  authors: {
    a: {
      …
    }
  },
  comments: {
    z: {
      …
    }
  },
}

这样,数据结构看起来就不在那么深层嵌套了,当你需要改变数据的时候,就可以轻松的实现数据的不可变性了。

normalizr 是个强大的
library,可以帮助我们进行数据格式化,噢耶~!

函数式和面向对象

Redux更多的是遵循函数式编程(Functional Programming,
FP)思想,而Mobx则更多从面相对象角度考虑问题。

Redux提倡编写函数式代码,如reducer就是一个纯函数(pure
function),如下:

(state, action) => { return Object.assign({}, state, { … }) }

1
2
3
4
5
(state, action) => {
  return Object.assign({}, state, {
    …
  })
}

纯函数,接受输入,然后输出结果,除此之外不会有任何影响,也包括不会影响接收的参数;对于相同的输入总是输出相同的结果。

Mobx设计更多偏向于面向对象编程(OOP)和响应式编程(Reactive
Programming),通常将状态包装成可观察对象,于是我们就可以使用可观察对象的所有能力,一旦状态对象变更,就能自动获得更新。

发表评论

电子邮件地址不会被公开。 必填项已用*标注