展示组件和容器组件

react-redux绑定库是基于容器组件和展示组件分离的开发思想,是react开发中非常重要的一个思想。

详细介绍可以查看本文

#展示组件和容器组件对比

展示组件 容器组件
作用 描述如何展现(骨架、样式) 描述如何运行(数据获取、状态更新)
直接使用 Redux
数据来源 props 监听 Redux state
数据修改 从 props 调用回调函数 想 Redux 派发actions
调用方式 手动 通常由 React Redux 生成

进行组件分离的好处

1.更好地分散注意力。通过这样写组件能更好地理解你的 app 和 UI。

2.更好地重用。你可以使用同样的表式组件配合完全不同的状态源,并在未来重用这些分离的容器组件。

3.展示组件对你 APP 的“调色”是很有必要的。你可以把它们放在单页里,并让设计师在不触碰逻辑的情况下自由地调整配色。

4.这强迫你抽出类似于:Sidebar,Page,ContextMenu,这样的布局组件并使用 this.props.children 而不是重复在几个容器组件中同样的标记和布局。

#什么时候该引入容器组件?

我建议你一开始只使用展示组件来构建你的APP。最终你将意识到你传递了太多 props 给中间的组件。当你意识到有些组件并没有用到它们接收的 props,而只是将这些 props向下传递。在底层的组件需要更多数据时,你不得不重写所有这些中间组件,这时候就很适合引入容器组件了。通过这种方式,你可以将 props 传递给叶子节点而不增加和它无关的中层组件的负担。

提示

不要把展示组件和容器组件的划分当做教条。有些时候甚至无关紧要。如果你不确定一个组件是该定义成展示组件还是容器组件,也许是区分得太早了,不用担心。

React初学者,你需要知道这些

一、React 技术栈

所有的软件都构建在一系列的技术栈上,你需要足够理解构建你 app 的技术栈。React 的技术栈看起来很庞大的原因在于它总是被按照错误的顺序解释了。
你应该按下列的顺序来学习,不要跳过或者同时学习它们:

  • React 基础
  • npm
  • JavaScript “bundlers”(webpack)
  • ES6
  • Routing
  • Flux

你不需要一次性学完它们。仅仅在遇到需要解决的问题时进行下一步的学习。
额外地,有一些经常在 React 社区中被提及的前沿技术,它们非常有趣,但很难理解。和上面的主题比起来没有那么受欢迎并且在大部分 app 开发中也用不上。

  • Inline styles
  • Server rendering
  • Immutable.js
  • Relay, Falcor, etc

二、React 只是一个视图库

React 不是 MVC 框架,也不同于其他任何框架。它只是一个用来渲染你视图的库。如果你来自 MVC 的世界,你需要意识到 React 只是’V’,且是部分等于。你需要在其他地方找到你的 ‘M’ 和 ‘C’。不然你终将会在令人生厌的 React 代码前止步。

三、让组件尽可能的小

这点是显而易见的,但也值得讨论。每一个优秀的开发者都知道小的类或模块更容易理解,测试和维护。对于 React 组件来说也是一样。在开始使用 React 时会有一个疑问,那就是我的组件到底该要多小?显然,确定的尺寸取决于很多因素(包括你和你的团队成员的偏好),但通常我的建议是让你的组件比你认为的还要小很多。比如下面这个用于展示我最近博客推送的组件:

1
2
3
4
5
6
7
8
const LatestPostsComponent = props => (
<section>
<div><h1>Latest posts</h1></div>
<div>
{ props.posts.map(post => <PostPreview key={post.slug} post={post}/>) }
</div>
</section>
);

这个组件本身是一个 <section>,仅有2个<div>在里面。第一个是标题,第二个映射了一些数据,为每一个元素渲染了 <PostPreview>。我认为这是一个组件适合的尺寸。

四、写函数组件

之前有定义 React 组件有两种选择,第一种是 React.createClass():

1
2
3
4
5
const MyComponent = React.createClass({
render: function() {
return <div className={this.props.className}/>;
}
});

另一种是 ES6 的类:

1
2
3
4
5
class MyComponent extends React.Component {
render() {
return <div className={this.props.className}/>;
}
}

React 0.14 引入了寻得定义组件的语法:

1
2
3
const MyComponent = props => (
<div className={props.className}/>
);

这是至今我最喜欢的定义 React 组件的方法。除了更加简洁的语法,这种方法对组件需要被分离时很有帮助。让我们看一个例子,假设我们没有进行组件分离:

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
class LatestPostsComponent extends React.Component {
render() {
const postPreviews = renderPostPreviews();

return (
<section>
<div><h1>Latest posts</h1></div>
<div>
{ postPreviews }
</div>
</section>
);
}

renderPostPreviews() {
return this.props.posts.map(post => this.renderPostPreview(post));
}

renderPostPreview(post) {
return (
<article>
<h3><a href={`/post/${post.slug}`}>{post.title}</a></h3>
<time pubdate><em>{post.posted}</em></time>
<div>
<span>{post.blurb}</span>
<a href={`/post/${post.slug}`}>Read more...</a>
</div>
</article>
);
}
}

这种写法并不是很糟糕。我们已经从 render() 方法中抽取了大量方法,并保证每一块代码都尽可能小并很好地命名。我们已经将 renderPostPreviews()方法封装地足够好了。让我们使用函数语法重写这段代码:

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
const LatestPostsComponent = props => {
const postPreviews = renderPostPreviews(props.posts);

return (
<section>
<div><h1>Latest posts</h1></div>
<div>
{ postPreviews }
</div>
</section>
);
};

const renderPostPreviews = posts => (
posts.map(post => this.renderPostPreview(post))
);

const renderPostPreview = post => (
<article>
<h3><a href={`/post/${post.slug}`}>{post.title}</a></h3>
<time pubdate><em>{post.posted}</em></time>
<div>
<span>{post.blurb}</span>
<a href={`/post/${post.slug}`}>Read more...</a>
</div>
</article>
);

两段段代码几乎一致,只是后面的没有了类的声明。然而对我来说这是很大的区别。在基于类的例子中,我会看到 class LatestPostsComponent { ,并自然地向下扫直到大花括号,也就是类定义结束的地方,也是组件定义结束的地方。而当我看函数组件时,,我看到const LatestPostsComponent = props => { ,仅仅扫到函数定义结束的地方。“函数定义在这里结束了,所以组件定义也结束了”。我这样想着,“但是等等,其他在组件外且在同一模块下的代码呢?哦!!这是其它用于获取数据和渲染视图的函数,我可以将它们全部提取到一个组件中。”
这是一种让人非常舒服的,让人遵守“让组件尽可能小”的方式。
在未来会有对 React 的进一步优化,这会让函数组件比基于类的组件更加高效。
值得注意的是函数组件有一些’限制’,我认为这让它变得更强大。第一点是函数组件无法赋予 ref 属性。ref是组件查询子节点并与之通讯的便捷的方式,而我却觉得这是写 React 代码的一种错误方式。ref鼓励一种非常命令式地,类似于 jquery 的方式来写组件,把我们带离了最初选择 React 的函数式的,单向数据流的哲学。
另一点是函数组件没有与之相关联的 state,这也是一个非常大的优点。

五、写无状态组件

可以这么说,至今为止我写 React 代码的几乎所有痛苦都来自于组件有太多的状态。
状态让组件很难测试
实践表明,没有什么比测试纯粹的、data-in data-out 函数更容易的了,所以我们不要为组件定义那么多状态。当测试有状态的组件时,我们需要让组件进入“合适”的状态以测试它们的行为。我们同样需要考虑所有状态(组件可以在任何时候被修改)和属性(组件无法控制)的组合,并决定测试哪一个。当组件只是输入属性的函数时,测试会变得简单许多。
状态使得在组件中添加业务代码变得很容易
让组件来决定程序的行为是我们在任何情况下都不应该考虑的。记住,React 只是一个视图库,在组件中写渲染逻辑是没问题的,但不能写业务逻辑(业务逻辑意味着大量代码)。但是当你的组件中有大量应用程序的状态并且可以轻易地通过 this.state访问时,你就会不自觉地将计算或者验证类的代码写入组件。这会让测试的工作变得非常困难。
状态使得在 app 的其他部分共享信息变得困难
当一个组件有一些状态时,它很容易在组件间传递状态,但向其它方向传递会变得很棘手。

六、使用 Redux.js

React 是一个视图库,那么问题来了:“我在哪里放我的状态和逻辑呢?”
没错, Redux.js 可以替你做这些。下面是 React 的工作流程:
1.组件被给予了用做回调函数的属性,在 UI 事件触发时调用。
2.这些回调函数根据事件来创建并分发动作(action)
3.reducer执行动作,并计算新的状态
4.整个应用得新的状态将会保存在 single store
5.组件接受新的状态并在它们必要时重新渲染。
Redux 的几点好处:

  • reducer 是纯函数,简单地执行 oldState + action = newState。每一个 reducer 计算一部分状态并将它们组合到一起生成整个应用。这使得你的业务逻辑和状态变换很容易测试。
  • API 很小,很简单并且很好文档化。我可以迅速地进行查找并很容易学习全部的原理,之后就能更容易理解我的工程的动作和信息流。
  • 如果你按推荐的方式使用它,仅仅只有很少的一部分组件需要依赖 Redux。其他组件只需要接受属性。这使得组件很简单。
    还有一些用于补充 Redux 的库如下:
  • Immutable.js - 用于 JavaScript 的不可变数据结构。将你的状态保存在这里,使得状态在它不应该被改变的时候不改变,并确保 reducer 的纯净。
  • redux-thunk - 用于你的动作不改变状态的时候。例如:调用一个 REST API 或者设置路由,或者分发其他动作。

七、总是使用 propTypes

propTypes 为我们提供了一种非常容易的方式来为组件更安全地添加类型。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const ListOfNumbers = props => (
<ol className={props.className}>
{
props.numbers.map(number => (
<li>{number}</li>)
)
}
</ol>
);

ListOfNumbers.propTypes = {
className: React.PropTypes.string.isRequired,
numbers: React.PropTypes.arrayOf(React.PropTypes.number)
};

它有一下几点好处:

  • 更容易捕获 bugs,以避免愚蠢的错误。
  • 如果你使用 isRequired,你就不必总是检查 undefinednull
  • 它类似于文档,其他开发者就不必看完整个组件来看属性是否必须了。

八、使用 React 和 Redux dev tools

React 和 Redux dev tools 都是非常棒的工具。React dev tool 是你能够查阅已经渲染的 React 元素树,这对于查看浏览器中的视图很有帮助。Redux dev tool 更加出色,使你能查看发生的每一个动作,以及动作造成的状态改变,甚至给你能够回溯的能力。
你也可以设置热模块来替代 webpack,使你的页面在你的代码改变时就更新-不需要浏览器主动刷新。这使得反馈循环更加地高效。

JavaScript摘要

JavaScript摘要

1.基本数据类型

JavaScript的基本构建块包括:对象(object)、原语(primitive)和字面量(literal)。

literal 表示特定类型的一个值,例如:String, Number, or Boolean。

1
2
3
"this is a string"
1.45
true

Primitive 是一个特定数据类型的实例,包括五种:String, Number, Boolean, null, undefined。

JavaScript 的世界中,一切皆对象(object)。

2.基本语法

函数声明:

1
2
3
4
5
function distance(x1, y1, x2, y2) {
var dx = x2 - x1;
var dy = y2 - y1;
return Math.sqrt(dx*dx + dy*dy);
}

条件语句:

1
2
3
4
5
6
7
if (x <= 1){ 
return 1;
}else{
return x*fact(x-1);
}

if (this.length == 0) return this;

循环语句:

1
2
3
4
5
6
7
8
9
10
11
for(i=0;i<10;i++){
tst+=i;
}

for (var i in obj) {
result += obj_name + "." + i + " = " + obj[i] + "<br>";
}

while((matchArray = pattern.exec(searchString)) != null) {
str+="at " + matchArray.index + " we found " + matchArray[0] + "\n";
}

3.数组

创建数组:

1
2
3
var arr = new Array(element0, element1, ..., elementN);
var arr = Array(element0, element1, ..., elementN);
var arr = [element0, element1, ..., elementN];

填充数组:

1
2
3
4
5
6
7
8
var emp = [];
emp[0] = "Casey Jones";
emp[1] = "Phil Lesh";
emp[2] = "August West";
var arr = [];
arr[3.4] = "Oranges";
console.log(arr.length); // 0
console.log(arr.hasOwnProperty(3.4)); // true

如果你在以上代码中给数组操作符的是一个非整形数值,那么将作为一个代表数组的对象的属性(property)创建,而非作为数组的元素。

引用数组元素:

1
2
3
var arr = ["one", "two", "three"];
arr[2]; // three
arr["length"]; // 3

数组操作符(方括号 [ ])也可以用来访问数组的属性(在 JavaScript 中,数组也是对象)。

遍历数组:

1
2
3
4
5
6
7
8
9
var colors = ['red', 'green', 'blue'];
for (var i = 0; i < colors.length; i++) {
console.log(colors[i]);
}

var colors = ['red', 'green', 'blue'];
colors.forEach(function(color) {
console.log(color);
});

数组对象的方法:

concat() 连接两个数组并返回一个新的数组。

join(deliminator = ',') 将数组的所有元素连接成一个字符串。

push() 在数组末尾添加一个或多个元素,并返回数组操作后的长度。

pop() 从数组移出最后一个元素,并返回该元素。

shift() 从数组移出第一个元素,并返回该元素。

4.集合类

Map

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var sayings = new Map();
sayings.set('dog', 'woof');
sayings.set('cat', 'meow');
sayings.set('elephant', 'toot');
sayings.size; // 3
sayings.get('fox'); // undefined
sayings.has('bird'); // false
sayings.delete('dog');
sayings.has('dog'); // false

for (var [key, value] of sayings) {
console.log(key + ' goes ' + value);
}
// "cat goes meow"
// "elephant goes toot"

sayings.clear();
sayings.size; // 0

Map和Object的比较:

一般地,objects会被用于将字符串类型映射到数值。Object允许设置键值对、根据键获取值、删除键、检测某个键是否存在。而Map具有更多的优势。

  • Object的键均为Strings类型,在Map里键可以是任意类型。
  • 必须手动计算Object的尺寸,但是可以很容易地获取使用Map的尺寸。
  • Map的遍历遵循元素的插入顺序。
  • Object有原型,所以映射中有一些缺省的键。(可以理解为map = Object.create(null))。

Set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var mySet = new Set();
mySet.add(1);
mySet.add("some text");
mySet.add("foo");

mySet.has(1); // true
mySet.delete("foo");
mySet.size; // 2

for (let item of mySet) console.log(item);
// 1
// "some text"
# Array 和 Set的转换
Array.from(mySet);
[...mySet2];

mySet2 = new Set([1,2,3,4]);

Array和Set的对比:

一般情况下,在JavaScript中使用数组来存储一组元素,而新的集合对象有这些优势:

  • 数组中用于判断元素是否存在的indexOf 函数效率低下。
  • Set对象允许根据值删除元素,而数组中必须使用基于下标的 splice 方法。
  • 数组的indexOf方法无法找到NaN值。
  • Set对象存储不重复的值,所以不需要手动处理包含重复值的情况。

5.字符串

String 字面量

1
2
'foo'
"bar"

String对象

String对象是对 String 类型的封装。

1
2
3
var s = new String("foo"); // Creates a String object
console.log(s); // Displays: { '0': 'f', '1': 'o', '2': 'o'}
typeof s; // Returns 'object'

模板字符串

1
2
3
4
5
var a = 5;
var b = 10;
console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
// "Fifteen is 15 and
// not 20."
方法 描述
charAt, charCodeAt, codePointAt 返回字符串指定位置的字符或者字符编码。
indexOf, lastIndexOf 分别返回字符串中指定子串的位置或最后位置。
startsWith, endsWith, includes 返回字符串是否以指定字符串开始、结束或包含指定字符串。
concat 连接两个字符串并返回新的字符串。
fromCharCode, fromCodePoint 从指定的Unicode值序列构造一个字符串。这是一个String类方法,不是实例方法。
split 通过将字符串分离成一个个子串来把一个String对象分裂到一个字符串数组中。
slice 从一个字符串提取片段并作为新字符串返回。
substring, substr 分别通过指定起始和结束位置,起始位置和长度来返回字符串的指定子集。

6.面向对象

基于类VS基于原型的语言

基于类的面向对象语言,比如 Java 和 C++,是构建在两个不同实体的概念之上的:即类和实例。

  • 类可以定义属性,这些属性可使特定的对象集合特征化(可以将 Java 中的方法和变量以及 C++ 中的成员都视作属性)。类是抽象的,而不是其所描述的对象集合中的任何特定的个体。例如 Employee 类可以用来表示所有雇员的集合。
  • 另一方面,一个实例是一个类的实例化;也就是其中一名成员。例如, Victoria 可以是 Employee 类的一个实例,表示一个特定的雇员个体。实例具有和其父类完全一致的属性。

基于原型的语言(如 JavaScript)并不存在这种区别:它只有对象。基于原型的语言具有所谓原型对象的概念。原型对象可以作为一个模板,新对象可以从中获得原始的属性。任何对象都可以指定其自身的属性,既可以是创建时也可以在运行时创建。而且,任何对象都可以作为另一个对象的原型,从而允许后者共享前者的属性。

差异总结

基于类的(Java) 基于原型的(JavaScript)
类和实例是不同的事物。 所有对象均为实例。
通过类定义来定义类;通过构造器方法来实例化类。 通过构造器函数来定义和创建一组对象。
通过 new 操作符创建单个对象。 相同。
通过类定义来定义现存类的子类,从而构建对象的层级结构。 指定一个对象作为原型并且与构造函数一起构建对象的层级结构
遵循类链继承属性。 遵循原型链继承属性。
类定义指定类的所有实例的所有属性。无法在运行时动态添加属性。 构造器函数或原型指定初始的属性集。允许动态地向单个的对象或者整个对象集中添加或移除属性。
  1. 首先了解该语言的基本数据类型,基本语法和主要语言构造,主要数学运算符和print函数的使用,达到能够写谭浩强程序设计书课后数学习题的程度;
  2. 其次掌握数组和其他集合类的使用,有基础的话可以理解一下泛型,如果理解不了也问题不大,后面可以补;
  3. 简单字符串处理。所谓简单,就是Regex和Parser以下的内容,什么查找替换,截断去字串之类的。不过这个阶段有一个难点,就是字符编码问题。如果理解不了,可以先跳过,否则的话最好在这时候把这个问题搞定,免留后患;
  4. 基本面向对象或者函数式编程的特征,无非是什么继承、多态、Lambda函数之类的,如果有经验的话很快就明白了;
  5. 异常、错误处理、断言、日志和调试支持,对单元测试的支持。你不一定要用TDD,但是在这个时候应该掌握在这个语言里做TDD的基本技能;
  6. 程序代码和可执行代码的组织机制,运行时模块加载、符号查找机制,这是初学时的一个难点,因为大部分书都不太注意介绍这个极为重要的内容;
  7. 基本输入输出和文件处理,输入输出流类的组织,这通常是比较繁琐的一部分,可以提纲挈领学一下,搞清楚概念,用到的时候查就是了。到这个阶段可以写大部分控制台应用了;
  8. 该语言如何进行callback方法调用,如何支持事件驱动编程模型。在现代编程环境下,这个问题是涉及开发思想的一个核心问题,几乎每种语言在这里都会用足功夫,.NET的delegate,Java的anonymous inner class,Java 7的closure,C++OX的 tr1::function/bind,五花八门。如果能彻底理解这个问题,不但程序就不至于写得太走样,而且对该语言的设计思路也能有比较好的认识;
  9. 如果有必要,可在这时研究regex和XML处理问题,如无必要可跳过;
  10. 序列化和反序列化,掌握一下缺省的机制就可以了;
  11. 如果必要,可了解一下线程、并发和异步调用机制,主要是为了读懂别人的代码,如果自己要写这类代码,必须专门花时间严肃认真系统地学习,严禁半桶水上阵;
  12. 动态编程,反射和元数据编程,数据和程序之间的相互转化机制,运行时编译和执行的机制,有抱负的开发者在这块可以多下些功夫,能够使你对语言的认识高出一个层面;
  13. 如果有必要,可研究一下该语言对于泛型的支持,不必花太多时间,只要能使用现成的泛型集合和泛型函数就可以了,可在以后闲暇时抽时间系统学习。需要注意的是,泛型技术跟多线程技术一样,用不好就成为万恶之源,必须系统学习,谨慎使用,否则不如不学不用;
  14. 如果还有时间,最好咨询一下有经验的人,看看这个语言较常用的特色features是什么,如果之前没学过,应当补一下。比如Ruby的block interator, Java的dynamic proxy,C# 3的LINQ和extension method。没时间的话,我认为也可以边做边学,没有大问题。
  15. 有必要的话,在工作的闲暇时间,可以着重考察两个问题,第一,这个语言有哪些惯用法和模式,第二,这个语言的编译/解释执行机制。
Your browser is out-of-date!

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

×