doc: first commit

This commit is contained in:
ruanyf
2017-04-09 10:10:41 +08:00
commit bedeb3f466
17 changed files with 1818 additions and 0 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
React 教程

185
docs/component.md Normal file
View File

@@ -0,0 +1,185 @@
# 组件
## 概述
React通过“组件”component构成一个页面。
组件通常是下面的样子。
```javascript
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
}
}
```
React原生提供的组件都是HTML语言已经定义的标签比如`<div>``<h1>``<p>`等等。这些组件的首字母必须小写。用户自定义的组件,首字母必须大写,比如`<MyTitle>`
也可以采用命名空间的方式,引用组件。
```javascript
const App = () => (
<MUI.RaisedButton label="Default" />
);
```
组件有以下属性。
- `this.props` 组件的参数
- `this.state` 组件的状态
`props``state`的主要区别是,`state`是组件私有的内部参数不应暴露到外部。这就是为什么父组件或者外部使用者不能操作子组件的state`setState`方法只能在当前组件内部使用)。
### setState
组件的状态,通过`setState`方法改变。每当`state`发生变化,`render`方法就会自动调用,从而更新组件。
它接受一个对象作为参数,这个对象会被合并进入`this.state`,然后重新渲染组件。
```javascript
this.setState({mykey: 'my new value'});
```
它也可以接受一个函数作为参数,该函数执行后返回的对象会合并进`this.state`
```javascript
this.setState(function (previousState, currentProps) {
return {myInteger: previousState.myInteger + 1};
});
```
从上面代码可以看到,回调函数的参数是更新的状态和当前的参数对象。
`this.setState`这个方法是异步的,即`this.state`的变化,要在该方法执行后一段时间才能看到。这一点需要特别引起注意。
```javascript
class Select extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {selection: 0};
}
render() {
return (
<ul>
<li onClick={() => this.onSelect(1)}>1</li>
<li onClick={() => this.onSelect(2)}>2</li>
<li onClick={() => this.onSelect(3)}>3</li>
</ul>
)
}
onSelect(value) {
this.setState({selection: value});
console.log(this.state.selection);
}
}
```
上面代码中,每次点击会触发`this.setState`方法,但是`this.state`并没有立即改变,返回的仍然是上一次的值。
所以,`setState`方法可以接受一个回调函数当作第二个参数,当状态改变成功、组件重新渲染后,立即调用。
```javascript
this.setState(
{...},
() => console.log('set state!')
)
```
`this.setState`总是会引起组件的重新渲染,除非`shouldComponentUpdate()`方法返回`false`。有时`this.setState`设置的状态在`render`方法中并没有用到,即不改变 UI 的呈现,但是这时也会引起组件的重新渲染。
### defaultProps
`defaultProps`用来指定`props`的默认值。
```javascript
class Account extends Component {
static defaultProps = {
email: '',
gender: '',
};
constructor(props) {
super(props);
}
render() {
const { name, email, gender } = this.props;
return (
<div className="mod-account">
<p>Name: {name}</p>
<p>Email: {email}</p>
<p>Gender: {gender}</p>
</div>
)
}
}
```
注意,这时`constructor`方法里面,不能再对设置了默认值的属性赋值,否则会报错。
### ref
`ref`属性可以指定一个回调函数在组件加载到DOM之后调用。
```javascript
render: function() {
return <TextInput ref={(c) => this._input = c} />;
},
componentDidMount: function() {
this._input.focus();
},
```
`ref`作为一个函数,它的参数就是组件加载后生成的 DOM 节点。
下面是另外一个例子。
```javascript
<input type="text"
ref={
(e) => e ? e.selectionStart = this.props.task.length : null
}
autoFocus={true}
defaultValue={this.props.task}
onBlur={this.finishEdit}
onKeyPress={this.checkEnter} />;
```
`ref`属性也可以设为一个字符串,比如`ref="input"`,然后通过`this.refs.input`引用。
## 子组件
父组件内部的组件,称为子组件。
```html
<Parent><Child /></Parent>
```
上面代码中,`Parent`是父组件,`Child`是子组件。
`Parent`可以通过`this.props.children`属性,读取子组件。
同级而且同类型的每个子组件,应该有一个`key`属性,用来区分。
```javascript
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.text}</li>;
})}
</ol>
);
}
```
上面代码中,子组件`li``key`用来区分每一个不同的子组件。

143
docs/concept.md Normal file
View File

@@ -0,0 +1,143 @@
# 基本概念
## 组件
UI使用数据结构表示。UI的变化就是一种数据结构变成另一种数据结构。
同样的数据结构就得到同样的UI。这就是说UI是一个纯函数。
```javascript
function NameBox(name) {
return { fontWeight: 'bold', labelContent: name };
}
```
一个大的UI组件可以包含小的组件。
```javascript
function FancyUserBox(user) {
return {
borderStyle: '1px solid blue',
childContent: [
'Name: ',
NameBox(user.firstName + ' ' + user.lastName)
]
};
}
```
各种小组件可以组合成一个大组件composition
同一个组件可以有内部状态,即有多种数据结构表示。
## 元素
在React中类就是组件component`element`是描述组件实例的对象。
`element`并不是真的实例。Element上面不能调用任何方法。它只是实例的一个不可变immutable描述带有两个属性`type``props`
如果`type`属性是一个字符串,`element`就代表一个DOM节点`type`就是它的标签名,`props`是它的属性。
```javascript
{
type: 'button',
props: {
className: 'button button-blue',
children: {
type: 'b',
props: {
children: 'OK!'
}
}
}
}
```
上面代码对应下面的HTML代码结构。
```html
<button class='button button-blue'>
<b>
OK!
</b>
</button>
```
`children`属性就是它的子元素。
如果`type`属性是函数或对应React组件的类。
```javascript
{
type: Button,
props: {
color: 'blue',
children: 'OK!'
}
}
```
多个子组件可以组合放入`children`属性。
```javascript
const DeleteAccount = () => ({
type: 'div',
props: {
children: [{
type: 'p',
props: {
children: 'Are you sure?'
}
}, {
type: DangerButton,
props: {
children: 'Yep'
}
}, {
type: Button,
props: {
color: 'blue',
children: 'Cancel'
}
}]
});
```
对应的JSX代码如下。
```javascript
const DeleteAccount = () => (
<div>
<p>Are you sure?</p>
<DangerButton>Yep</DangerButton>
<Button color='blue'>Cancel</Button>
</div>
);
```
组件可以是一个类,也可以是返回一个类的函数。
```javascritpt
const Button = ({ children, color }) => ({
type: 'button',
props: {
className: 'button button-' + color,
children: {
type: 'b',
props: {
children: children
}
}
}
});
```
## element
`element`是一个描述组件如何显示在屏幕上的对象。`element`可以在`props`里面包含其他`element`。一旦创建,就不再发生变化。
`component`可以是一个带有`render`方法的类,也可以定义为一个函数。它总是接受`props`作为输入,在输出之中返回一个`element`树。
## 参考链接
- [React - Basic Theoretical Concepts](https://github.com/reactjs/react-basic), by sebmarkbage

146
docs/context.md Normal file
View File

@@ -0,0 +1,146 @@
# Context的用法
`Context`是用于解决组件层层传递参数的问题。
```javascript
var Button = React.createClass({
render: function() {
return (
<button style={{background: this.props.color}}>
{this.props.children}
</button>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div>
{this.props.text} <Button color={this.props.color}>Delete</Button>
</div>
);
}
});
var MessageList = React.createClass({
render: function() {
var color = "purple";
var children = this.props.messages.map(function(message) {
return <Message text={message.text} color={color} />;
});
return <div>{children}</div>;
}
});
```
上面的代码需要层层传递`color`参数,这很不方便。`context`属性可以解决这个问题。
首先,改写根组件。
```javascript
var MessageList = React.createClass({
childContextTypes: {
color: React.PropTypes.string
},
getChildContext: function() {
return {color: "purple"};
},
render: function() {
var children = this.props.messages.map(function(message) {
return <Message text={message.text} />;
});
return <div>{children}</div>;
}
});
```
上面代码中,根组件添加`childContextTypes``getChildContext`属性React自动将`context`信息传向子树上的所有组件。
然后,子组件获取`context`属性。
```javascript
var Button = React.createClass({
contextTypes: {
color: React.PropTypes.string
},
render: function() {
return (
<button style={{background: this.context.color}}>
{this.props.children}
</button>
);
}
});
```
子组件通过定义`contextTypes`属性,可以获取`context`对象上的信息。如果`contextTypes`属性没有定义,那么`this.context`将是一个空对象。
如果组件内部定义了`contextTypes`方法,那么生命周期方法会接收到一个额外的参数`context`对象。
```javascript
void componentWillReceiveProps(
object nextProps, object nextContext
)
boolean shouldComponentUpdate(
object nextProps, object nextState, object nextContext
)
void componentWillUpdate(
object nextProps, object nextState, object nextContext
)
void componentDidUpdate(
object prevProps, object prevState, object prevContext
)
```
无状态的函数组件也能够引用`context`对象,如果`contextTypes`被定义成函数的一个属性。
```javascript
function Button(props, context) {
return (
<button style={{background: context.color}}>
{props.children}
</button>
);
}
Button.contextTypes = {color: React.PropTypes.string};
```
只要`state``props`发生变化,`getChildContext`就会被调用。后代组件就会接收到一个新的`context`对象。
```javascript
var MediaQuery = React.createClass({
getInitialState: function(){
return {type:'desktop'};
},
childContextTypes: {
type: React.PropTypes.string
},
getChildContext: function() {
return {type: this.state.type};
},
componentDidMount: function(){
var checkMediaQuery = function(){
var type = window.matchMedia("(min-width: 1025px)").matches ? 'desktop' : 'mobile';
if (type !== this.state.type){
this.setState({type:type});
}
};
window.addEventListener('resize', checkMediaQuery);
checkMediaQuery();
},
render: function(){
return this.props.children;
}
});
```
`context`属性有点像全局变量,轻易不能使用,你应该宁可将参数一层层显式传给子组件。
最佳的使用场合是传递用户的语言选择、样式选择,否则这些值就要被当作全局变量传入组件。
不要使用`context`将你的Modal数据传给组件。显式传递更容易理解。使用`context`将造成组件的强耦合,代码难以复用。

6
docs/css.md Normal file
View File

@@ -0,0 +1,6 @@
# CSS
```jsx
const style = {color: red}
<div style={style}></div>
```

29
docs/form.md Normal file
View File

@@ -0,0 +1,29 @@
# 表单
下面的例子是如何获取输入框的值。
```javascript
var Message = React.createClass({
getInitialState: function() {
return { message: this.props.message };
},
_messageChange: function(e) {
this.setState({ message: e.target.value });
},
render: function() {
return (
<div>
<span>Message: {this.state.message}</span>
<br />
Message: <input type="text"
value={this.state.message}
onChange={this._messageChange} />
</div>
);
},
});
```
上面代码中,输入框的内容一旦发生变化,就会调用`onChange`属性指定的监听函数,该函数的参数会得到事件对象。从事件对象`e.target.value`就可以得到输入框的值。

33
docs/graphql.md Normal file
View File

@@ -0,0 +1,33 @@
# GraphQL的用法
GraphQL是一种全新的数据查询语句的写法。
## 简介
我们希望取回下面的数据。
```javascript
{
"id": "c49bc1e1-26c1-49c5-b9c5-c89e24be0ac4",
"url": "https://pics.example.com/dog",
"caption": "My Dog",
"author": {
"id": "5a0cba48-c014-4cf0-b6fb-0bf7514b8165",
"name": "Alice"
}
}
```
GraphQL就可以写成下面的样子。
```javascript
node(id: "c49bc1e1-26c1-49c5-b9c5-c89e24be0ac4") {
id,
url,
caption,
author {
id,
name
}
}
```

62
docs/jsx.md Normal file
View File

@@ -0,0 +1,62 @@
# JSX 语法
## 概述
组件的最外层,只能有一个标签。因为,一个组件就相当于调用一个构造函数,如果有多个标签,就变成一个组件对应多个构造函数了。
组件的标签一定要闭合。只要有开标签,就一定要有闭标签,比如`<div>`一定要有对应的`</div>`。没有子元素的标签,必须写成自闭合形式(比如`<img/>`),如果写成单个的`<img>`就会报错。
JSX会对HTML字符串转义。如果不希望被转义可以采用下面的写法。
```javascript
<div dangerouslySetInnerHTML={{__html: 'cc &copy; 2015'}} />
```
## 注释
JSX的注释采用`/* ... */`的形式。
在子组件的位置,注释必须放入大括号。
```javascript
const App = <div> {/* 这是注释 */} </div>;
```
在属性的位置,也可以放置注释。
```javascript
<Person
/* 多行
注释 */
name={name}
/>
```
## 属性
属性有三种写法。
```javascript
// 写法一
const component = <Component name={name} value={value} />;
// 写法二
const component = <Component />;
component.props.name = name;
component.props.value = value;
// 写法三
const data = { name: 'foo', value: 'bar' };
const component = <Component {...data} />;
```
组件的属性基本上与HTML语言定义的属性名一致。但是下面两个属性名因为是JavaScript关键字因此改用下面的名字。
- `class`:改为`className`
- `for`:改为`htmlFor`
如果要向原生组件提供自定义属性,要写成`data-`前缀。
```javascript
<div data-attr="xxx">content</div>
```

245
docs/life-cycle.md Normal file
View File

@@ -0,0 +1,245 @@
# 生命周期方法
## 执行顺序
首次挂载组件时,按如下顺序执行。
- getDefaultProps
- getInitialState
- componentWillMount
- render
- componentDidMount
卸载组件时,按如下顺序执行。
- componentWillUnmount
重新挂载组件时,按如下顺序执行。
- getInitialState
- componentWillMount
- render
- componentDidMount
再次渲染组件时,组件接受到父组件传来的新参数,按如下顺序执行。
- componentWillReceiveProps
- shouldComponentUpdate
- componentWillUpdate
- render
- componentDidUpdate
如果组件自身的 state 更新了,按如下顺序执行。
1. shouldComponentUpdate
1. componentWillUpdate
1. render
1. componentDidUpdate
## componentDidUpdate
`componentDidUpdate`方法在每次组件重新渲染后执行。
```javascript
componentDidUpdate: function() {
this.scrollElement();
},
render: function() {
return [...]
```
上面代码在组件每次重新渲染后,会执行`this.scrollElement`方法。
这里有一个问题,如果`this.scrollElement`里面需要操作 DOM这时很可能 DOM 还没有完全生成。因此,可以使用`requestAnimationFrame``setTimeout`方法,保证操作时 DOM 已经生成。
```javascript
scrollElement: function() {
var _this = this;
setTimeout(function () {
window.requestAnimationFrame(function() {
var node = _this.getDOMNode();
if (node !== undefined) {
node.scrollTop = node.scrollHeight;
}
})
}, 0)
},
componentDidMount: function() {
this.scrollElement();
},
```
## shouldComponentUpdate
`shouldComponentUpdate`方法会在每次重新渲染之前,自动调用。它返回一个布尔值,决定是否应该进行此次渲染。默认为`true`,表示进行渲染,如果为`false`,就表示中止渲染。
下面是父元素组件`Color`的代码。
```javascript
getInitialState: function () {
return {
colors: new Immutable.List(this.props.colors)
};
},
_addColor: function (newColor) {
this.setState({
colors = this.state.colors.push(newColor)
});
},
render: function () {
return (
<div>
<ColorList colors={this.state.colors} />
<ColorForm addColor={this._addColor} />
</div>
);
}
```
上面代码中,父组件`Color`向子组件`ColorList`传入参数`this.state.colors`。每当父组件的`this.state.colors`变动时,子组件就会重新渲染,这时子组件的`shouldComponentUpdate`就会自动调用。
```javascript
shouldComponentUpdate: function (nextProps, nextState) {
return nextProps.colors !== this.props.colors;
}
```
上面是子组件的`shouldComponentUpdate`方法,它接受两个参数,第一个是本次传入的新参数对象`nextProps`,第二个是新的状态对象`nextState`。在方法内部,`this.props``this.state`表示当前(没有重新渲染之前)的参数对象和状态对象。
注意,`shouldComponentUpdate`方法默认返回`true`,这意味着即使`state``props`没有改变,只要调用`this.setState`,就会导致组件重新渲染。
## componentWillReceiveProps
`componentWillReceiveProps`方法在父组件每次要求当前组件重新渲染时调用,它在当前组件的`render()`之前调用。它只在父组件更新 `props`时执行,当前组件本身调用`setState`而引发重新渲染,是不会执行这个方法的。在此方法中调用 setState 是不会二次渲染的。
`componentWillReceiveProps``shouldComponentUpdate``componentWillUpdate`之前调用。
`componentWillReceiveProps`之中,可以调用`setState`方法。而`componentWillUpdate`是一个方法用来回应state的变化。
这个方法可以用来在`render()`调用之前对props进行调整然后通过`this.setState()`设置state。老的props可以用`this.props`拿到。
```javascript
componentWillReceiveProps: function(nextProps) {
this.setState({
likesIncreasing: nextProps.likeCount > this.props.likeCount
});
}
```
父组件操作子组件的基本流程如下。
- 父组件向子组件传入一个新参数
- 子组件在`componentWillReceiveProps`方法里面处理新的参数,必要时调用`setState`方法
- 子组件在`componentWillUpdate`方法里面处理新的state但是一般来说只使用`componentWillReceiveProps`方法就足够了
下面是一个父组件。
```javascript
function rand(arr) {
return arr[Math.floor(Math.random() * arr.length)];
}
var ADayInTheLife = React.createClass({
getInitialState: function () {
return {
doing: "isCoding"
};
},
handleButtonClick: function () {
var activities = ["isCoding", "isEating", "isSleeping"];
var randAct = rand(activities);
this.setState({
doing: randAct
});
},
render: function () {
return (
<div>
<button onClick={this.handleButtonClick}>Next Activity</button>
<Jake activity={this.state.doing} />
</div>
);
}
});
React.render(<ADayInTheLife />, document.body);
```
上面代码中,父组件`ADayInTheLife`会更新子组件`Jake`
下面是子组件`Jake`的代码。
```javascript
var Jake = React.createClass({
getDefaultProps: function () {
return {
activity: "nothingYet"
};
},
getInitialState: function () {
return {
thinking: "nothing yet"
};
},
calcThinking: function (activity) {
var thoughts = {
isCoding: "yay, code",
isEating: "yum, code",
isSleeping: "where's the code?"
};
this.setState({
thinking: thoughts[activity]
})
},
componentWillReceiveProps: function (nextProps) {
this.calcThinking(nextProps.activity);
},
render: function () {
return <div>Jake: <b>{this.props.activity}</b> and thinking "{this.state.thinking}".</div>;
}
});
```
上面代码中,每次父组件要求`Jake`重新渲染,`componentWillReceiveProps`方法就会被调用。它执行`calcThinking`方法,再执行`setState`方法,使得`Jake`根据新的state完成渲染。
## componentWillMount
`componentWillMount`方法在第一次渲染之前调用。它只会执行一次,在浏览器和服务器都会执行。一般用来对`props``state`进行初始化处理。
```javascript
getInitialState: function() {
return {
items: this.props.initialItems || [],
sort: this.props.config.sort || { column: "", order: "" },
columns: this.props.config.columns
};
},
componentWillMount: function() {
this.loadData(this.props.dataSource);
},
loadData: function(dataSource) {
if (!dataSource) return;
$.get(dataSource).done(function(data) {
console.log("Received data");
this.setState({items: data});
}.bind(this)).fail(function(error, a, b) {
console.log("Error loading JSON");
});
},
```
上面代码中,`getInitialState`为首次渲染设置默认参数。在首次渲染之前,会执行`componentWillMount`方法。该方法内部调用`loadData`方法发出AJAX请求。这个请求有可能成功也有可能不成功而且不知道需要多久才能完成。在AJAX请求返回结果执行`setState`方法设置新的state之前该组件将以默认值渲染。所以使用`getInitialState`方法设置默认参数的意义就在这里。
注意,该方法之中不能调用`setState`

67
docs/react-router.md Normal file
View File

@@ -0,0 +1,67 @@
# React-Router
安装
```bash
$ npm install react-router
```
基本用法
```jsx
import { Router, Route } from 'react-router';
React.render((
<Router>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="users" component={Users}>
<Route path="/user/:userId" component={User}/>
</Route>
<Route path="*" component={NoMatch}/>
</Route>
</Router>
), document.body);
```
完整的例子。
```jsx
// first we import some components
import { Router, Route, Link } from 'react-router';
// the histories are imported separately for smaller builds
import { history } from 'react-router/lib/HashHistory';
var App = React.createClass({
render () {
return (
<div>
<h1>App</h1>
{/* change the <a>s to <Links>s */}
<ul>
<li><Link to="/about">About</Link></li>
<li><Link to="/inbox">Inbox</Link></li>
</ul>
{/*
next we replace `<Child>` with `this.props.children`
the router will figure out the children for us
*/}
{this.props.children}
</div>
)
}
});
// Finally we render a `Router` component with some `Route`s, it'll do all
// the fancy routing stuff for us.
React.render((
<Router history={history}>
<Route path="/" component={App}>
<Route path="about" component={About}/>
<Route path="inbox" component={Inbox}/>
</Route>
</Router>
), document.body);
```

37
docs/reactdom.md Normal file
View File

@@ -0,0 +1,37 @@
# ReactDOM
`ReactDOM`只有三个方法。
- findDOMNode
- unmountComponentAtNode
- render
## findDOMNode()
`findDOMNode`方法用于获取真实的DOM元素。
```javascript
componentDidMount() {
// this 为当前组件的实例
const dom = ReactDOM.findDOMNode(this);
}
```
该方法只对已经挂载的组件有效,因此一般只在`componentDidMount``componentDidUpdate`方法中使用。
## render()
`render`方法用于将组件挂载到真实DOM之中。
```javascript
ReactComponent render(
ReactElement element,
DOMElement container,
[function callback]
)
```
该方法把元素挂载到指定的DOM节点并且返回该组件的实例。如果是无状态组件`render`会返回`null`。当组件挂载完毕,会执行回调函数。

15
docs/redux.md Normal file
View File

@@ -0,0 +1,15 @@
# Redux的用法
## 基本原则
所有状态存放在一个地方。
## Reducer
Reducer描述状态如何变化。它不改变原来的状态每次都生成一个新的状态。
```javascript
function exampleReducer(state, action) {
return state.changedBasedOn(action)
}
```

22
docs/static-method.md Normal file
View File

@@ -0,0 +1,22 @@
# 静态方法
## React.createElement
`React.createElement`方法用来生成一个React组件实例。它的第一个参数是React组件类第二个参数是一个对象表示生成实例的参数。
```javascript
React.createElement(Layout, {
title: 'React Static Site',
children: 'Hello World'
});
```
## ReactDOMServer.renderToString
`react-dom/server`模块`renderToString`方法用来将一个React组件转成HTML字符串一般用于服务器渲染。
如果`ReactDOM.render()`在一个已经完成服务器渲染的DOM节点上面挂载React组件那么该组件不会挂载只会添加事件监听到这个DOM节点。
## ReactDOMServer.renderToStaticMarkup
`ReactDOMServer.renderToStaticMarkup`方法与`renderToString`方法非常像除了不会生成React专用的一些属性比如`data-react-id`

161
docs/testing.md Normal file
View File

@@ -0,0 +1,161 @@
# React测试
React组件至少有两个方面需要测试。
- 给定属性property)和状态state会渲染出什么结果
- 对于渲染的结果进行某种输入用户的互动有没有可能从状态A转化成状态B
第一类测试可以称为测试结构,第二类测试可以称之为测试行为。
推荐的工具集
- Shallow rendering
- Regular test utilities and jsdom when needed
- mocha + jsdom + expect + expect-jsx + babel-plugin-rewire
## 测试结构真实DOM测试
`mocha-jsdom`模块让你可以在`describe`方法内部或者全局环境,使用`jsdom()`方法将Node.js环境模拟成一个浏览器环境支持所有DOM和浏览器的API`window``document``history`等等变量在全局环境下可用。
`jsdom`的参数`useEach`表示这些全局变量绑定Mocha的`beforeEach`/`afterEach`方法,而不是`before`/`after`方法,默认等于`false`
`react-addons-test-utils`模块是React官方提供的测试工具库。它的`renderIntoDocument`方法接受一个React组件作为参数将其渲染成Document对象上面的一个真实DOM结构但是不会将其插入文档。这个方法执行的前提是`window``window.document``window.document.createElement`必须在调用React之前就在全局范围之中可以拿到。否则React会认为无法拿到DOM然后诸如`setState`这样的方法就不可用。
`react-dom`模块的`findDOMNode`方法它接受一个React组件作为参数。如果该组件已经加到了DOM那么该方法返回对应的浏览器之中的真实DOM。这个方法主要用于从真实方法取值比如表单项的值。大多数情况从组件的`ref`属性可以拿到真实DOM不一定要使用`findDOMNode`方法。如果`render`方法返回`null``false``findDOMNode`方法也会返回`null`
`findDOMNode()`只对已经加载的组件有效即已经放入DOM否则会报错。
```javascript
import React from 'react';
import {findDOMNode} from 'react-dom';
import {renderIntoDocument} from 'react-addons-test-utils';
import jsdom from 'mocha-jsdom';
import expect from 'expect';
class Label extends React.Component {
render() {
return <span>Hello {this.props.name}</span>;
}
}
class Button extends React.Component {
render() {
return <div><Label name={this.props.name} /></div>;
}
}
describe('Real dom test', () => {
jsdom({useEach: true});
it('works', () => {
let component = renderIntoDocument(<Button name="John" />);
let DOMNode = findDOMNode(component);
expect(
DOMNode.querySelector('span')
.textContent
).toEqual('Hello John');
});
});
```
例二。
```javascript
import React from 'react';
import {
renderIntoDocument,
findRenderedDOMComponentWithTag
} from 'react-addons-test-utils';
import jsdom from 'mocha-jsdom';
import expect from 'expect';
class Label extends React.Component {
render() {
return <span>Hello {this.props.name}</span>;
}
}
class Button extends React.Component {
render() {
return <div><Label name={this.props.name} /></div>;
}
}
describe('Real Test Utilities', () => {
jsdom({useEach: true});
it('works', () => {
let component = renderIntoDocument(<Button name="John" />);
expect(
findRenderedDOMComponentWithTag(component, 'span')
.textContent
).toEqual('Hello John');
});
});
```
例三。
```javascript
import React from 'react';
import expect from 'expect';
import {createRenderer} from 'react-addons-test-utils';
class Label extends React.Component {
render() {
return <span>Hello {this.props.name}</span>;
}
}
class Button extends React.Component {
render() {
return <div><Label name={this.props.name} /></div>;
}
}
describe('Shallow rendering', () => {
it('works', () => {
let renderer = createRenderer();
renderer.render(<Button name="John" />);
let actualElement = renderer.getRenderOutput();
let expectedElement = <div><Label name="John" /></div>;
expect(actualElement).toEqual(expectedElement);
});
});
```
## 测试行为
```javascript
import React from 'react';
import expect from 'expect';
import {createRenderer} from 'react-addons-test-utils';
class Label extends React.Component {
render() {
return <span>Hello {this.props.name}</span>;
}
}
class Button extends React.Component {
render() {
return <div onClick={this.props.click}><Label name={this.props.name} /></div>;
}
}
describe('Shallow rendering on* handlers', () => {
it('works', () => {
let renderer = createRenderer();
let hasClicked = false;
let click = () => hasClicked = true;
renderer.render(<Button name="John" click={click} />);
renderer.getRenderOutput().props.onClick();
expect(hasClicked).toBe(true);
});
});
```
## 参考链接
- Vincent Voyer, [Our journey through React component unit testing](http://slides.com/vvo/react-component-unit-testing/)
- Marcin Grzywaczewski, [Approaches to testing React components - an overview](http://reactkungfu.com/2015/07/approaches-to-testing-react-components-an-overview/)

223
docs/typescript.md Normal file
View File

@@ -0,0 +1,223 @@
# TypeScript的用法
## 基本用法
```bash
$ npm install --global typescript
```
使用TypeScript编写的脚本后缀名为`.ts``tsc`命令会将TS脚本转为正常的JavaScript脚本程序。
```bash
# 在当前目录生成 example.js
$ tsc example.ts
```
## 基本概念
TypeScript 提供9种数据类型。
- Boolean
- Number
- String
- Array
- Tuple
- Enum
- Any
- Void
- Function
定义变量。
```javascript
// 定义基本类型的值
const lie : boolean = false;
const pi : number = 3.14159;
const tree_of_knowledge : string = "Yggdrasil";
// 定义数组有两种写法
const divine_lovers : string[] = ["Zeus", "Aphrodite"];
const digits : Array<number> = [143219876, 112347890];
```
Tuple类似于数组但是成员数量是已知的`[(Item Type), . . . , (Item Type)]`
```javascript
let date_triplet : [number, number, number];
date_triplet = [31, 6, 2016];
let athena : [string, number];
athena = ['Athena', 9386];
var name : string = athena[0];
const age : number = athena[1];
```
Enums是一个常量集合。创建一个Enum就创建了一个数据的新类型。
```javascript
enum Color { Red, Green, Blue };
const red : Color = Color.Red;
console.log(Color[0]); // 'Red'
// TypeScript允许指定起始序号
enum RomanceLanguages { Spanish = 1, French, Italian, Romanian, Portuguese };
console.log(RomanceLanguages[4]); // 'Romanian'
console.log(RomanceLanguages[0]); // undefined
```
Any类型表示接受任何值。
```javascript
let mystery : any = 4; // number
mystery = "four"; // string -- no error
const not_only_strings : any[] = [];
not_only_strings.push("This Works!")
not_only_strings.push(42); // This does too.
```
Void类型只能接受undefined和null作为值主要用来标记不返回任何值的函数返回值的类型。
```javascript
let the_void : void = undefined;
the_void = null;
the_void = "nothing"; // Error
```
Function的声明。
```javascript
function capitalizeName (name : string) : string {
return name.toUpperCase();
}
let multiply : (first : number, second : number) => number;
multiply = function (first, second) {
return first * second;
}
let multiplyFirst : ( first : number) => ((second : number) => number);
multiplyFirst = function (first) {
return function (num) {
return first * num;
}
}
```
## Class
下面的代码在TypeScript编译时会报错。
```javascript
class ListComponent {
constructor () {
this.things = [];
}
}
```
TypeScript规定当通过`this`,为实例分配属性时,必须添加属性定义。
```javascript
class ListComponent {
things : Array<ListItem>; // Or -- things : ListItem[];
constructor () {
this.things = [];
}
}
```
实例的属性还可以用`public``private`修饰。
```javascript
class ListComponent {
public things : Array<ListItem>; // Or -- things : ListItem[];
constructor () {
this.things = [];
}
}
class Person {
constructor (
public first_name : string,
public last_name : last_name
) { }
}
// 等同于
class Person {
public first_name : string;
public last_name : string;
constructor (
first_name : string,
last_name : last_name
) { }
}
```
`private`的例子。
```javascript
class ListComponent {
private _things : Array<ListItem>;
constructor () {
this._things = [];
}
get things () : Array<ListItem> { return this._things; }
}
```
TypeScript的继承与ES6的继承是一致的。
```javascript
class CompletedListItem extends ListItem {
completed : boolean;
constructor (name : string) {
super(name);
this.completed = true;
}
}
```
Interface用来定义和描述Class。
```javascript
interface User {
name : string;
email : string;
}
class RegisteredUser implements User {
constructor (
public name : string,
public email : string
) { }
}
```
属性名后面如果有问号,就表示可以不部署。
```javascript
interface User {
name : string;
email : string;
avatar? : Object;
}
// 指定必须部署的方法
interface User {
// PROPERTIES
name : string;
email : string;
avatar? : Object;
// API
print () : void;
}
```
## 参考链接
- Peleke Sengstacke, [From JavaScript to TypeScript, Pt. IIA: Using Classes, Interfaces, & Mixins](https://scotch.io/tutorials/from-javascript-to-typescript-pt-iia-using-classes-interfaces-mixins)

393
docs/webpack.md Normal file
View File

@@ -0,0 +1,393 @@
# Webpack
Webpack 是一个打包器,英语叫做 bundler意思是将不同的脚本打包成一个文件浏览器可以运行这个文件。
Webpack 的特色。
- 能将依赖的模块区分成不同的代码块chunk按需加载。
- 能将静态资源(样式表、图片、字体等等),像加载模块那样加载。
## 基本用法
Webpack 的基本用法,就是将多个文件打包成一个文件。
```bash
$ webpack ./src/App.js ./build/bundle.js
```
上面代码将`App.js`及其依赖的脚本,打包成一个文件`bundle.js`
假定`App.js`的代码如下。
```javascript
let $ = require('jquery');
$("p").css({ color: 'red'});
```
那么,打包出来的`bundle.js`就会同时包含`App.js``jquery`的代码。
## 命令行用法
`--watch``-w`表示监视功能。一旦发现脚本有变动,就立刻重新构建。
```bash
$ webpack -w js/profile.js dist/bundle.js
$ webpack --watch
```
`--devtool`用来指定如何生成 Source map 文件。
```bash
$ webpack -w --devtool source-map js/profile.js dist/bundle.js
```
`--config`参数用来指定配置文件。
```bash
$ webpack --config ./path/to/webpack.dev.js
```
## 配置文件 webpack.config.js
Webpack 的配置文件默认为`webpack.config.js`。它采用 CommonJS 模块格式,输出一个配置对象。
下面是一个例子。
```javascript
module.exports = {
entry: '...',
output: '...',
// ...
};
```
Webpack 配置对象包含多个字段。
### entry 字段
`entry`字段指定打包的入口脚本。它可以是一个字段,一个数组,也可以是一个对象。
```javascript
entry: 'app.js',
// 或者
entry: [
'react-hot-loader/patch',
'webpack-dev-server/client?http://localhost:8080',
'webpack/hot/only-dev-server',
'./src/client.jsx'
],
```
`entry`的值是对象时,通常用于与`output`字段配合,打出多个包。
```javascript
module.exports = {
entry: {
desktop: './src/desktop.js',
mobile: './src/mobile.js'
},
output: {
path: './dist',
filename: '[name].bundle.js',
// 多个 bundle.js 共享的异步加载部分
chunkFilename: '[id].common.js'
}
};
```
### output 字段
`output`字段指定打包后的输出。
```javascript
output: {
// 输出路径,即打包后的文件的输出路径
path: './dist',
// 输出文件名
filename: 'bundle.js',
// 浏览器的实际加载的路径,
// 即浏览器到什么位置下载打包后的文件
publicPath: '/',
// 模块的输出格式,下面是指定输出为全局变量
libraryTarget: "var",
// 输出的全局变量的名字
library: "Foo"
},
```
Webpack 允许文件名包含哈希,比如编译后生成形如`47a0cc1b840cb310842cb85fb5b6116c.js`的脚本名。只要文件发生过变更,哈希就会不同,这对长时间缓存文件很有帮助。
```javascript
module.exports = {
entry: './src/main.js',
output: {
path: './dist',
filename: '[hash].js'
}
};
```
### externals 字段
`externals`字段指定哪些模块不需要打包,全局环境本身会提供。
```javascript
// 下面指定不要打包 jquery由全局环境提供
externals: {
"jquery": "jQuery"
},
```
### module 字段
有些文件在打包前,需要先进行处理。比如,某些新的语法需要 Babel 转码。`module.rules`用来指定转码规则。
```javascript
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' }
]
},
```
### resolve 字段
`resolve`字段是一个对象,规定了模块解析的设置。
`resolve.modulesDirectories`设置模块加载时,依次搜索的目录。
```javascript
resolve: {
modulesDirectories: ['node_modules', 'bower_components', 'web_modules']
}
```
`resolve.extensions`设置脚本文件的后缀名。
```javascript
resolve: {
extensions: ['.js', '.jsx']
},
```
### plugins 字段
`plugins`指定打包时需要的插件。
## require()
`require`支持模块加载以后,执行回调函数。
```javascript
require(['animals'], function(Animal) {
var bear = new Animal('bear');
});
```
## require.ensure()
某些脚本只是在一些特定情况下运行,比如用户点击某个按钮后运行。这时,如果把它和其他必备的脚本打包在一起,会增大打包后的体积。理想的方法是视情况而定,只有满足条件时才加载。
这种动态加载就要求,该脚本与其他脚本分开打包。`require.ensure`方法就是用来指定分开打包的脚本。
```javascript
require('./a');
if (condition) {
require.ensure([], function(require){
require('./b');
console.log('done!');
});
}
```
上面代码中,`a.js`会与`b.js`打在两个包里面,默认是`bundle.js``0.bundle.js`。网页运行时只加载`bundle.js`,然后由`bundel.js`根据`condition`,动态加载`0.bundle.js`
`require.ensure`方法接受三个函数。
第一个参数是一个数组,表示`0.bundle.js`依赖的模块,你可以不输入任何值,表示没有任何依赖。
第二个参数是一个回调函数,该函数将在`0.bundle.js`加载后执行。该函数的参数`require`函数,凡是在函数体内用`require`加载的模块都会被打包进入`0.bundle.js`
第三个参数是一个字符串,表示当前`require.ensure`打包的这段代码的名字,用于使用多个`require.ensure`时,所有代码可以打包成一个文件,避免打包成多个文件。
```javascript
require.ensure([], function(require){
require('./a');
}, 'foo');
require.ensure(['foo'], function(require){
require('./b');
});
```
上面代码中,两段`require.ensure`将打包在一个文件里面。
注意,`require.ensure`的第一个参数只用来作为依赖模块,如果不用到它是不会运行的。
```javascript
require.ensure(['./b.js'], function(require) {
require('./c.js');
});
```
上面代码中,如果`c.js`没有用到`b.js`,那么`b.js`是不会运行的,但是会打包在`0.bundle.js`里面。所以,不要随意把模块写在第一个参数里面。
## Loader
Webpack 的强大在于,它可以通过`require`加载任何东西。默认情况下Webpack 认为加载的是 JavaScript 文件。如果不是,就要通过 Loader 变形。
```javascript
var css = require('css!./css/style.css');
// 等同于
var css = require('css-loader!./css/style.css');
```
上面的`css-loader`会将CSS文件转为一个字符串。
多个 Loader 可以连用。
```javascript
require('style!css!./css/style.css');
```
上面的 style-loader会将CSS字符串转成一个`link`标签。
loader的参数
可以在配置文件里面指定同一类文件都使用某个loader。
```javascirpt
module.exports = {
module: {
loaders: [
{ test: /\.css$/, loader: 'style!css' }
]
}
};
```
还可以排除某些文件。
```javascript
{ test: /\.js$/,
exclude: /(node_modules|bower_components)/,
loader: 'babel-loader',
query: {
presets: ['es2015']
}
},
```
现在只要直接加载`.css`文件就可以了。
```javscript
require('./css/style.css');
```
还可以设置 preloader
```javascript
preLoaders: [
{ test: /\.js?$/, exclude: /node_modules/, loader: 'eslint-loader', include: __dirname + '/' }
],
```
## Plugin
插件都用于特定用途。
[压缩插件](http://webpack.github.io/docs/list-of-plugins.html#uglifyjsplugin)
```javascript
plugins: [
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
})
]
```
[提取文本](https://github.com/webpack/extract-text-webpack-plugin):将特定代码提取为一个文件
```javascript
module.exports = {
.....,
module: {
loaders: [
// for less files
{ test: /\.less$/,
exclude: /(node_modules|bower_components)/,
loader: ExtractTextPlugin.extract("style-loader", "css-loader", "less-loader")
}
]
},
plugins: [
new ExtractTextPlugin("style.css")
]
}
```
## Node API
Webpack 可以在 Node 环境中调用,它是一个构造函数,接受一个配置对象作为参数,生成 Webpack 实例。
```javascript
var webpack = require('webpack');
var WebpackDevServer = require('webpack-dev-server');
var config = require('./webpack.config');
var server = new WebpackDevServer(webpack(config), {
contentBase: './dev',
publicPath: config.output.publicPath,
hot: true
});
server.listen(3000, 'localhost', function (err, result) {
if (err) {
console.log(err);
}
console.log('Listening at localhost:3000');
});
```
```javascript
// require the webpack Node.js library
var webpack = require('webpack');
webpack({
// The first argument is your webpack config
entry: './src/entry.js',
output: {
path: './dist',
filename: 'bundle.js'
}
}, function(err, stats) {
// The second argument is a callback function that returns
// more information about your bundle when it is complete
});
```
另一种写法。
```javascript
var webpack = require('webpack');
// Create an instance of the compiler
var compiler = webpack({ /* webpack config */ });
// Run the compiler manually
compiler.run(function(err, stats) { });
```
`watch`方法可以监视源文件的变动后重新编译。
```javascript
compiler.watch(/* watchDelay */ 200, function(err, stats) { });
```
## 参考链接
- [Module Bundling with Webpack: Getting Started Guide](https://www.codementor.io/javascript/tutorial/module-bundler-webpack-getting-started-guide)by Chimeremeze Ukah

50
docs/webpackdevserver.md Normal file
View File

@@ -0,0 +1,50 @@
# WebpackDevServer
WebpackDevServer在脚本中调用时第一个参数是Webpack的实例第二个参数是配置对象。
```javascript
var server = new WebpackDevServer(webpack(config), {
contentBase: './dev',
publicPath: config.output.publicPath,
hot: true
});
server.listen(3000, 'localhost', function (err, result) {
if (err) {
console.log(err);
}
console.log('Listening at localhost:3000');
});
```
- `contentBase`属性指定HTTP服务器对外访问的主目录即源文件应该在这个目录。
- `publicPath`属性指定静态资源的目录,它是针对网站根目录的,而不是针对服务器根目录。比如,设定`publicPath: "/assets/"``file: "bundle.js"`以后,`bundle.js`的位置就是`/assets/bundle.js`
## 配置文件
`webpack.config.js`里面的`devServer`字段,用来配置 Dev server。
- hot是否打开热替换
- contentBase存放静态文件的位置
- publicPath存放打包文件的位置一般与`output.publicPath`相同。
- historyApiFallback发生 404 错误时打开的位置
下面是一个例子。
```javascript
devServer: {
contentBase: './dist',
// 是否开启 hot-loader
hot: true,
// 是否兼容BrowserHistory
historyApiFallback: true
},
```
Dev server 的配置,也可以在命令行设置。
```bash
$ webpack-dev-server --inline --hot --content-base ./path/to/index.html
```