From bedeb3f466118557510b148b8d59e802af7fef04 Mon Sep 17 00:00:00 2001 From: ruanyf Date: Sun, 9 Apr 2017 10:10:41 +0800 Subject: [PATCH] doc: first commit --- README.md | 1 + docs/component.md | 185 ++++++++++++++++++ docs/concept.md | 143 ++++++++++++++ docs/context.md | 146 +++++++++++++++ docs/css.md | 6 + docs/form.md | 29 +++ docs/graphql.md | 33 ++++ docs/jsx.md | 62 ++++++ docs/life-cycle.md | 245 ++++++++++++++++++++++++ docs/react-router.md | 67 +++++++ docs/reactdom.md | 37 ++++ docs/redux.md | 15 ++ docs/static-method.md | 22 +++ docs/testing.md | 161 ++++++++++++++++ docs/typescript.md | 223 ++++++++++++++++++++++ docs/webpack.md | 393 +++++++++++++++++++++++++++++++++++++++ docs/webpackdevserver.md | 50 +++++ 17 files changed, 1818 insertions(+) create mode 100644 README.md create mode 100644 docs/component.md create mode 100644 docs/concept.md create mode 100644 docs/context.md create mode 100644 docs/css.md create mode 100644 docs/form.md create mode 100644 docs/graphql.md create mode 100644 docs/jsx.md create mode 100644 docs/life-cycle.md create mode 100644 docs/react-router.md create mode 100644 docs/reactdom.md create mode 100644 docs/redux.md create mode 100644 docs/static-method.md create mode 100644 docs/testing.md create mode 100644 docs/typescript.md create mode 100644 docs/webpack.md create mode 100644 docs/webpackdevserver.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..ddec8b7 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +React 教程 diff --git a/docs/component.md b/docs/component.md new file mode 100644 index 0000000..a24faba --- /dev/null +++ b/docs/component.md @@ -0,0 +1,185 @@ +# 组件 + +## 概述 + +React通过“组件”(component),构成一个页面。 + +组件通常是下面的样子。 + +```javascript +export default class App extends React.Component { + + constructor(props) { + super(props); + this.state = {}; + } + + render() { + } +} +``` + +React原生提供的组件,都是HTML语言已经定义的标签,比如`
`、`

`、`

`等等。这些组件的首字母必须小写。用户自定义的组件,首字母必须大写,比如``。 + +也可以采用命名空间的方式,引用组件。 + +```javascript +const App = () => ( + +); +``` + +组件有以下属性。 + +- `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 ( +

    +
  • this.onSelect(1)}>1
  • +
  • this.onSelect(2)}>2
  • +
  • this.onSelect(3)}>3
  • +
+ ) + } + + 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 ( +
+

Name: {name}

+

Email: {email}

+

Gender: {gender}

+
+ ) + } +} +``` + +注意,这时`constructor`方法里面,不能再对设置了默认值的属性赋值,否则会报错。 + +### ref + +`ref`属性可以指定一个回调函数,在组件加载到DOM之后调用。 + +```javascript + render: function() { + return this._input = c} />; + }, + componentDidMount: function() { + this._input.focus(); + }, +``` + +`ref`作为一个函数,它的参数就是组件加载后生成的 DOM 节点。 + +下面是另外一个例子。 + +```javascript + 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`可以通过`this.props.children`属性,读取子组件。 + +同级而且同类型的每个子组件,应该有一个`key`属性,用来区分。 + +```javascript + render: function() { + var results = this.props.results; + return ( +
    + {results.map(function(result) { + return
  1. {result.text}
  2. ; + })} +
+ ); + } +``` + +上面代码中,子组件`li`的`key`用来区分每一个不同的子组件。 diff --git a/docs/concept.md b/docs/concept.md new file mode 100644 index 0000000..60e0913 --- /dev/null +++ b/docs/concept.md @@ -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 + +``` + +`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 = () => ( +
+

Are you sure?

+ Yep + +
+); +``` + +组件可以是一个类,也可以是返回一个类的函数。 + +```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 diff --git a/docs/context.md b/docs/context.md new file mode 100644 index 0000000..bba70e7 --- /dev/null +++ b/docs/context.md @@ -0,0 +1,146 @@ +# Context的用法 + +`Context`是用于解决组件层层传递参数的问题。 + +```javascript +var Button = React.createClass({ + render: function() { + return ( + + ); + } +}); + +var Message = React.createClass({ + render: function() { + return ( +
+ {this.props.text} +
+ ); + } +}); + +var MessageList = React.createClass({ + render: function() { + var color = "purple"; + var children = this.props.messages.map(function(message) { + return ; + }); + return
{children}
; + } +}); +``` + +上面的代码需要层层传递`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 ; + }); + return
{children}
; + } +}); +``` + +上面代码中,根组件添加`childContextTypes`和`getChildContext`属性,React自动将`context`信息传向子树上的所有组件。 + +然后,子组件获取`context`属性。 + +```javascript +var Button = React.createClass({ + contextTypes: { + color: React.PropTypes.string + }, + render: function() { + return ( + + ); + } +}); +``` + +子组件通过定义`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.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`将造成组件的强耦合,代码难以复用。 diff --git a/docs/css.md b/docs/css.md new file mode 100644 index 0000000..8e932d1 --- /dev/null +++ b/docs/css.md @@ -0,0 +1,6 @@ +# CSS + +```jsx +const style = {color: red} +
+``` diff --git a/docs/form.md b/docs/form.md new file mode 100644 index 0000000..869f742 --- /dev/null +++ b/docs/form.md @@ -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 ( +
+ Message: {this.state.message} +
+ Message: +
+ ); + }, +}); +``` + +上面代码中,输入框的内容一旦发生变化,就会调用`onChange`属性指定的监听函数,该函数的参数会得到事件对象。从事件对象`e.target.value`就可以得到输入框的值。 diff --git a/docs/graphql.md b/docs/graphql.md new file mode 100644 index 0000000..10b3c66 --- /dev/null +++ b/docs/graphql.md @@ -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 + } +} +``` diff --git a/docs/jsx.md b/docs/jsx.md new file mode 100644 index 0000000..2ccfb08 --- /dev/null +++ b/docs/jsx.md @@ -0,0 +1,62 @@ +# JSX 语法 + +## 概述 + +组件的最外层,只能有一个标签。因为,一个组件就相当于调用一个构造函数,如果有多个标签,就变成一个组件对应多个构造函数了。 + +组件的标签一定要闭合。只要有开标签,就一定要有闭标签,比如`
`一定要有对应的`
`。没有子元素的标签,必须写成自闭合形式(比如``),如果写成单个的``就会报错。 + +JSX会对HTML字符串转义。如果不希望被转义,可以采用下面的写法。 + +```javascript +
+``` + +## 注释 + +JSX的注释,采用`/* ... */`的形式。 + +在子组件的位置,注释必须放入大括号。 + +```javascript +const App =
{/* 这是注释 */}
; +``` + +在属性的位置,也可以放置注释。 + +```javascript + +``` + +## 属性 + +属性有三种写法。 + +```javascript +// 写法一 +const component = ; + +// 写法二 +const component = ; +component.props.name = name; +component.props.value = value; + +// 写法三 +const data = { name: 'foo', value: 'bar' }; +const component = ; +``` + +组件的属性,基本上与HTML语言定义的属性名一致。但是,下面两个属性名因为是JavaScript关键字,因此改用下面的名字。 + +- `class`:改为`className` +- `for`:改为`htmlFor` + +如果要向原生组件提供自定义属性,要写成`data-`前缀。 + +```javascript +
content
+``` diff --git a/docs/life-cycle.md b/docs/life-cycle.md new file mode 100644 index 0000000..10365ec --- /dev/null +++ b/docs/life-cycle.md @@ -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 ( +
+ + +
+ ); +} +``` + +上面代码中,父组件`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 ( +
+ + +
+ ); + } + +}); + +React.render(, 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
Jake: {this.props.activity} and thinking "{this.state.thinking}".
; + } +}); +``` + +上面代码中,每次父组件要求`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`。 diff --git a/docs/react-router.md b/docs/react-router.md new file mode 100644 index 0000000..75b5a21 --- /dev/null +++ b/docs/react-router.md @@ -0,0 +1,67 @@ +# React-Router + +安装 + +```bash +$ npm install react-router +``` + +基本用法 + +```jsx +import { Router, Route } from 'react-router'; + +React.render(( + + + + + + + + + +), 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 ( + + ) + } +}); + +// Finally we render a `Router` component with some `Route`s, it'll do all +// the fancy routing stuff for us. +React.render(( + + + + + + +), document.body); +``` + + diff --git a/docs/reactdom.md b/docs/reactdom.md new file mode 100644 index 0000000..400bfe5 --- /dev/null +++ b/docs/reactdom.md @@ -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`。当组件挂载完毕,会执行回调函数。 + + + diff --git a/docs/redux.md b/docs/redux.md new file mode 100644 index 0000000..c521883 --- /dev/null +++ b/docs/redux.md @@ -0,0 +1,15 @@ +# Redux的用法 + +## 基本原则 + +所有状态存放在一个地方。 + +## Reducer + +Reducer描述状态如何变化。它不改变原来的状态,每次都生成一个新的状态。 + +```javascript +function exampleReducer(state, action) { + return state.changedBasedOn(action) +} +``` diff --git a/docs/static-method.md b/docs/static-method.md new file mode 100644 index 0000000..8f5e1e3 --- /dev/null +++ b/docs/static-method.md @@ -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`。 diff --git a/docs/testing.md b/docs/testing.md new file mode 100644 index 0000000..7566dbf --- /dev/null +++ b/docs/testing.md @@ -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 Hello {this.props.name}; + } +} + +class Button extends React.Component { + render() { + return
; + } +} + +describe('Real dom test', () => { + jsdom({useEach: true}); + + it('works', () => { + let component = renderIntoDocument(