mirror of
https://github.com/wangdoc/react-tutorial.git
synced 2025-12-20 01:10:41 +08:00
doc: first commit
This commit is contained in:
185
docs/component.md
Normal file
185
docs/component.md
Normal 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
143
docs/concept.md
Normal 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
146
docs/context.md
Normal 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
6
docs/css.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# CSS
|
||||
|
||||
```jsx
|
||||
const style = {color: red}
|
||||
<div style={style}></div>
|
||||
```
|
||||
29
docs/form.md
Normal file
29
docs/form.md
Normal 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
33
docs/graphql.md
Normal 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
62
docs/jsx.md
Normal file
@@ -0,0 +1,62 @@
|
||||
# JSX 语法
|
||||
|
||||
## 概述
|
||||
|
||||
组件的最外层,只能有一个标签。因为,一个组件就相当于调用一个构造函数,如果有多个标签,就变成一个组件对应多个构造函数了。
|
||||
|
||||
组件的标签一定要闭合。只要有开标签,就一定要有闭标签,比如`<div>`一定要有对应的`</div>`。没有子元素的标签,必须写成自闭合形式(比如`<img/>`),如果写成单个的`<img>`就会报错。
|
||||
|
||||
JSX会对HTML字符串转义。如果不希望被转义,可以采用下面的写法。
|
||||
|
||||
```javascript
|
||||
<div dangerouslySetInnerHTML={{__html: 'cc © 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
245
docs/life-cycle.md
Normal 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
67
docs/react-router.md
Normal 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
37
docs/reactdom.md
Normal 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
15
docs/redux.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Redux的用法
|
||||
|
||||
## 基本原则
|
||||
|
||||
所有状态存放在一个地方。
|
||||
|
||||
## Reducer
|
||||
|
||||
Reducer描述状态如何变化。它不改变原来的状态,每次都生成一个新的状态。
|
||||
|
||||
```javascript
|
||||
function exampleReducer(state, action) {
|
||||
return state.changedBasedOn(action)
|
||||
}
|
||||
```
|
||||
22
docs/static-method.md
Normal file
22
docs/static-method.md
Normal 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
161
docs/testing.md
Normal 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
223
docs/typescript.md
Normal 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
393
docs/webpack.md
Normal 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
50
docs/webpackdevserver.md
Normal 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
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user