前端自动化测试之单元测试(二)—— react组件的测试工具jest

前面介绍了Polymer的测试框架web-components-tester, 今天来看看React团队出品的Jest.在此,特别感谢婆婆帮忙带宝宝才让我有时间继续书写文章.

Jest的功能

  • 内置jasmine
  • 内置mock函数 可以mock模块
  • 基于jsdom
  • 同步化书写异步代码
  • 真心赞一下简洁明了的API定义和用法 以及清晰的文档,确实让书写单元测试不再痛苦
  • 适用于commonJS模块的单元测试
  • 运行速度较慢

Jest用法

  • 安装: npm install jest-cli(需要node版本大于4)
  • 配置 package.json 如下
  • 运行: npm test
  • 调试(使用node-debug再浏览器中调试):node-debug –nodejs –harmony ./node_modules/jest-cli/bin/jest.js –runInBand tests/getUser-test.js
  • 运行单个文件 ./node_modules/jest-cli/bin/jest.js tests/getUser-test.js
{
  "name": "jest-test-examples",
  "version": "0.0.1",
  "dependencies": {},
  "scripts": {
    "test": "jest"
  }
}

下面就具体介绍一下使用jest进行测试的方法,采用的例子为jest的官方实例

一个简单的测试

jest.dontMock('../src/sum');
describe('sum', function() {
    it('adds 1 + 2 to equal 3', function() {
        var sum = require('../src/sum');
        expect(sum(1, 2)).toBe(3);
    });
});

describe和it还有expect都使用了jasmine的语法, jest会自动mock所有的依赖模块,对模块中所有的输出方法予以遍历并进行mock,对于要测试的模块使用jest.dontMock标识,jest就不会去mock.

异步的单元测试实例

异步是javascript的灵魂, 所以异步的测试也是极其重要的,下面看看jest关于异步程序的测试,假如有这样个ajax程序,获取数据并进行解析,对其进行测试

  • 1 ajax的数据获取是否正确
  • 2 parseUserJson是否正确
    在第二个测试用例中使用了genMockFunction,用来对回调函数进行mock,在jest中有两种方式进行函数的mock
    • 1 使用xFunc = require(‘xx’);
    • 2 使用xFunc = jest.genMockFunction();
      使用后,会在xFunc.calls中存储有关函数的调用信息,例如
    • //mock.calls.length 记录了函数被调用了几次
    • //mock.calls[0][0] 被调用函数的第一个参数
    • //mock.calls[0][1] 第二个参数
      代码如下: $.ajax是一个被mock的函数,callback也被mock,getUser(callback)调用后,可以通过检测传递的参数判断是否正确.
var $ = require('jquery');
function parseUserJson(userJson) {
    return {
        loggedIn: true,
        fullName: userJson.firstName + ' ' + userJson.lastName
    };
}
function fetchCurrentUser(callback) {
    return $.ajax({
        type: 'GET',
        url: 'http://example.com/currentUser',
        success: function(userJson) {
            callback(parseUserJson(userJson));
        }
    });
}
module.exports = fetchCurrentUser;
jest.dontMock('../src/getUser');
describe('getUser', function() {
    //test right params
    it('calls into $.ajax with the correct params', function() {
        var $ = require('jquery');
        var getUser = require('../src/getUser');
        function dummyCallback() {}
        getUser(dummyCallback);
        // Now make sure that $.ajax was properly called during the previous
        // 2 lines
        expect($.ajax).toBeCalledWith({
            type: 'GET',
            url: 'http://example.com/currentUser',
            success: jasmine.any(Function)
        });
    });

    //test callback function
    it('calls the callback when $.ajax requests are finished', function() {
        var $ = require('jquery');
        var getUser = require('../src/getUser');

        //create mock function
        var callback = jest.genMockFunction();
        getUser(callback);

        //xfunc.mock have interactions information
        //mock.calls.length call times
        //mock.calls[0][0] first param
        //mock.calls[0][1] second param
        //https://facebook.github.io/jest/docs/mock-functions.html

        //emulate the params pass to success
        $.ajax.mock.calls[0][0].success({
            firstName: 'Bobby',
            lastName: '");DROP TABLE Users;--'
        });

        expect(callback.mock.calls[0][0]).toEqual({
            loggedIn: true,
            fullName: 'Bobby ");DROP TABLE Users;--'
        });

    });
});

React组件的单元测试实例

假如我们有这样一个checkbox react组件, 如下, react的es6写法请参考我的blog-ES6的核心语法与应用
原理非常简单,点击checkbox切换label的状态.我们的测试代码如下, 使用了react-addons-test-utils这个模块, 模块的renderIntoDocument用于将react组件渲染到document中,
并且支持产生模拟事件:TestUtils.Simulate.change.

import React from 'react';
class Checkbox extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isChecked: false};
        this.changeState = this.changeState.bind(this);
    }
    changeState () {
        this.setState({isChecked: !this.state.isChecked})
    }
    render() {
        return (<label>
                <input type="checkbox" checked={this.state.isChecked} onChange={this.changeState} />
                {this.state.isChecked ? this.props.labelOn : this.props.labelOff}
               </label>)
    }

}
export default Checkbox;
import React from 'react';
import TestUtils from 'react-addons-test-utils';
import ReactDom from  'react-dom';
jest.dontMock('../src/checkbox');
const Checkbox = require('../src/checkbox');

describe('test react checkbox component', () => {
    it('change the label after click', () => {
        //1 render component
        //2 get node label get default value off
        //3 simulate click
        //4 expect value equal on

        //TestUtils.renderIntoDocument method refers to https://facebook.github.io/react/docs/test-utils.html#renderintodocument
        //TestUtils.findRenderedDOMComponentWithTag https://facebook.github.io/react/docs/test-utils.html#findrendereddomcomponentwithtag
        //ReactDom API:findDOMNode render unmountComponentAtNode server-side:renderToString renderToStaticMarkup

        var checkbox = TestUtils.renderIntoDocument(<Checkbox labelOn="On" labelOff="Off" />);
        var checkboxNode = ReactDom.findDOMNode(checkbox);
        //https://facebook.github.io/jest/docs/api.html#expect-value
        expect(checkboxNode.textContent).toEqual('Off');
        TestUtils.Simulate.change(TestUtils.findRenderedDOMComponentWithTag(checkbox, 'input'));
        expect(checkboxNode.textContent).toEqual('On');
    });
});

运行的时候我们需要通过babel预处理一下,通过如下的方式配置package.json即可运行:

  "scripts": {
    "test": "jest"
  },
  "jest": {
    "scriptPreprocessor": "<rootDir>/node_modules/babel-jest",
    "unmockedModulePathPatterns": [
      "<rootDir>/node_modules/react",
      "<rootDir>/node_modules/react-dom",
      "<rootDir>/node_modules/react-addons-test-utils"
    ],
    "modulePathIgnorePatterns": [
      "<rootDir>/node_modules/"
    ]
  }

手动mock

经常我们需要模拟某个模块中的方法(此方法实现非常复杂依赖第三方的模块)用来测试另一个模块的输入输出是否正确,jest就提供非常方便的mock机制,例如,我们在A模块中依赖jquery的fx方法
而fx方法又依赖于其他方法, 因为我们只关心fx的输出,所以我们就可以直接用来模拟,方法如下:

  • 建立mocks文件夹
  • 新建jquery模块:jquery.js
  • 使用genMockFromModule和mockImplementation API 如下
// mock the module of real jquery

var jqueryMocks = jest.genMockFromModule('jquery');
var mock_fx = function () {
    return 'mockValue';
};
//using mock_fx to mock the function of real fx
jqueryMocks.fx.mockImplementation(mock_fx);
module.exports = jqueryMocks;

这样就可以在测试代码中直接引用已经模拟好的fx函数进行测试了,直接对模块的输入控制,减少了依赖,实现测试的”解耦”.

describe('jest mocks', function () {
   it('jquery mock getEnv value', function(){
       var value = require('jquery').fx();
       expect(value).toEqual('mockValue')
   });
});

代码参考

源码

ES6的核心语法与应用

一直以来都对ES6嗤之以鼻,本来灵活简单的Javascrit,非得为了提升B格,增加学习的成本,搞那么多鸡肋的语法。但是无奈俺们这些“老年jser”都被历史的车轮碾压了,现在如果不掌握ES6,估计很多代码都看不懂了。没有闲暇的午后时间来系统的学习ES6(其实还是有点抵触心理),但是为了跟上“年轻人”的步伐,随着用随着看随着学吧。力求以最简单的语言讲述。

模块

定义模块

语法为:

  • export function x () {}
  • export class
  • export default {}
// kittydar.js - 找到一幅图像中所有猫的位置
    export function detectCats(canvas, options) {
      var kittydar = new Kittydar(options);
      return kittydar.detectCats(canvas);
    }
    export class Kittydar {
      ... 处理图片的几种方法 ...
    }
    // 这个helper函数没有被export。
    function resizeCanvas() {
      ...
    }
    //默认的
    export default {
        xx: '111'
    }

引用模块

 import {detectCats} from "kittydar.js"; //引入某个方法
 import {detectCats, Kittydar} from "kittydar.js"; //引入并重命名
 import * as module from './module';//引入全部全部
 import helloWorld from './hello-world'; //引入默认
 function go() {
        var canvas = document.getElementById("catpix");
        var cats = detectCats(canvas);
        drawRectangles(canvas, cats);
    }

class用法

  • 基本语法 class A {}
  • 构造器 constructor {}
  • 继承 class A extends AParent {}
  • super()
  • 注意:类声明与函数声明不同,它不会被提升,所以先new 后class定义 会抛出异常
  • 静态变量:static compare(a, b) {}
//ES5
//使用Object.defineProperty实现可读属性make year
function Vehicle(make, year) {
  Object.defineProperty(this, 'make', {
    get: function() { return make; }
  });

  Object.defineProperty(this, 'year', {
    get: function() { return year; }
  });
}

Vehicle.prototype.toString = function() {
  return this.make + ' ' + this.year;
}

var vehicle = new Vehicle('Toyota Corolla', 2009);
console.log(vehicle.make); // Toyota Corolla
vehicle.make = 'Ford Mustang'; //静态属性
console.log(vehicle.toString()) // Toyota Corolla 2009
//ES6
class Vehicle {
  constructor(make, year) {
    this._make = make;
    this._year = year;
  }

  get make() {
    return this._make;
  }

  get year() {
    return this._year;
  }

  toString() {
    return 'xxx';
  }
}

var vehicle = new Vehicle('Toyota Corolla', 2009);

console.log(vehicle.make); // Toyota Corolla
vehicle.make = 'Ford Mustang';
console.log(vehicle.toString()) // Toyota Corolla 2009
//ES5的继承
function Motorcycle(make, year) {
  Vehicle.apply(this, [make, year]);
}

Motorcycle.prototype = Object.create(Vehicle.prototype, {
  toString: function() {
    return 'xxx';
  }
});

Motorcycle.prototype.constructor = Motorcycle;

//ES6
class Motorcycle extends Vehicle {
  constructor(make, year) {
    super(make, year);
  }

  toString() {
    return 'xxxx';
  }
}

箭头函数

  • 箭头函数的产生,主要由两个目的:更简洁的语法和与父作用域共享关键字this。
  • function和{}都消失了,所有的回调函数都只出现在了一行里。
  • 当只有一个参数时,()也消失了(rest参数是一个例外,如(…args) => …)。
  • 当{}消失后,return关键字也跟着消失了。单行的箭头函数会提供一个隐式的return(这样的函数在其他编程语言中常被成为lamda函数)。
  • 箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域。
  • 箭头函数与普通函数还有一个区别就是,它没有自己的arguments变量,但可通过rest参数获得。
function () { return 1; }
() => { return 1; }
() => 1

function (a) { return a * 2; }
(a) => { return a * 2; }
(a) => a * 2
a => a * 2

function (a, b) { return a * b; }
(a, b) => { return a * b; }
(a, b) => a * b

function () { return arguments[0]; }
(...args) => args[0]

() => {} // undefined
() => ({}) // {}
//在之前的js中setInterval会把this指向window,
//使用箭头函数this使用外层的作用域所以不用保存this指针
$('.current-time').each(function () {
  setInterval(() => $(this).text(Date.now()), 1000);
});
//箭头函数的arguments,通过rest函数可以获得
function log(msg) {
  const print = (...args) => console.log(args[0]);
  print(`LOG: ${msg}`);
} 
log('hello'); // LOG: hello

作用域

javascript本身是没有块级作用域的,ES6新增的let语法替代var实现了块级作用域。

//before
function func(arr) {
    for (var i = 0; i < arr.length; i++) {
        // i ...
    }
    // 这里也可以访问到i
}
//ES6
function func(arr) {
    for (let i = 0; i < arr.length; i++) {
        // i ...
    }
    // 这里访问不到i
}

React on ES6

详情参考 这篇文章

定义组件

// The ES5 way
var Photo = React.createClass({
  handleDoubleTap: function(e) { … },
  render: function() { … },
});
// The ES6+ way
class Photo extends React.Component {
  handleDoubleTap(e) { … }
  render() { … }
}

componentWillMount关键字

// The ES5 way
var EmbedModal = React.createClass({
  componentWillMount: function() { … },
});
// The ES6+ way
class EmbedModal extends React.Component {
  constructor(props) {
    super(props);
    //实现componentWillMount内容的地方像dom操作
  }
}

state和props初始化

// The ES5 way
var Video = React.createClass({
  getDefaultProps: function() {
    return {
      autoPlay: false,
      maxLoops: 10,
    };
  },
  getInitialState: function() {
    return {
      loopsRemaining: this.props.maxLoops,
    };
  },
  propTypes: {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  },
});

// The ES6+ way
// static 实现只读的props
// 全局state
class Video extends React.Component {
  static defaultProps = {
    autoPlay: false,
    maxLoops: 10,
  }
  static propTypes = {
    autoPlay: React.PropTypes.bool.isRequired,
    maxLoops: React.PropTypes.number.isRequired,
    posterFrameSrc: React.PropTypes.string.isRequired,
    videoSrc: React.PropTypes.string.isRequired,
  }
  state = {
    loopsRemaining: this.props.maxLoops,
  }
}

react中的事件


class PostInfo extends React.Component { constructor(props) { super(props); // Manually bind this method to the component instance... this.handleOptionsButtonClick = this.handleOptionsButtonClick.bind(this); } handleOptionsButtonClick(e) { // this应指向实例 this.setState({showOptionsModal: true}); } } //箭头函数this指向外层的组件 class PostInfo extends React.Component { handleOptionsButtonClick = (e) => { this.setState({showOptionsModal: true}); } }

实例

使用ES6改写的React组件程序

import React from 'react'
class Checkbox extends React.Component {
    constructor(props) {
        super(props);
        this.state = {isChecked: false};
        this.changeState = this.changeState.bind(this);
    }
    changeState () {
        this.setState({isChecked: !this.state.isChecked})
    }
    render() {
        return (<label>
            <input type="checkbox" checked={this.state.isChecked} onChange={this.changeState} />
            {this.state.isChecked ? this.props.labelOn : this.props.labelOff}
        </label>)
    }

}
export default Checkbox;

React生命周期、API和深入用法

React火了很久了,一直都停留在照葫芦画瓢按照example凑数的基础上,但是如果真的要了解一个框架,它的生命周期和核心API是最重要的部分了,下面我们就来聊聊React的生命周期、核心API的用法以及React工具集,在使用React进行深度开发的时候,一定会事半功倍。本文假设你已经了解了React的基本开发知识,如果不了解,强烈推荐阮老师的这篇《React 入门实例教程》

一、生命周期

1、创建一个类和实例化的基础方法

 //创建组件类
var ComponentBox = React.createClass({
    //other lifecycle method
    render:  function () {
        //return JSX code
    }
});
//实例化组件
var compInstance = React.render(
      <ComponentBox />,
     document.getElementById('content') //DOM Element
);

2、了解this.state和this.prop

使用过React的人都会知道它有两个属性,state还有props,他们两者都可以作为render中的data输入源,那两者的区别、联系以及正确的用法又是怎么样的呢。参见这篇《props-vs-state》

  • prop是组件的配置项,是可选的,当组件接收这个参数后它就是不变的。组件改变不了它的props,父组件的props还担当着收集子组件配置项的功能。
  • state,在组件初始化的时候会赋予state初始的状态,当组件的状态发生变化的时候,组件内部自行管理着state的变化,所以state可以说是组件的私有值。state参数是可有可无的,但当你的组件是“Stateful Component”的时候你就应该考虑使用state了。

两者在组件变化的时候的状态改变如下:

- props state
Can get initial value from parent Component? Yes Yes
Can be changed by parent Component? Yes No
Can set default values inside Component?* Yes Yes
Can change inside Component? No Yes
Can set initial value for child Components? Yes Yes
Can change in child Components? Yes No

那么两者如何合理的使用和规划呢。举例来说,在官网《thinking-in-react》有这样的一个例子,组件如下图,当需要有过滤和搜索功能的时候,其实组件就是一个“具有状态的组件”了,需要有state管理对应的状态。通过分析,我们可以得到对应的变量规划:

  • 用于表示所有产品列表的 products
  • 用于表示过滤后的列表 (filterProducts)
  • 表示是否使用过滤的功能(checkValue)
  • 使用搜索过滤的关键词(searchValue)

要分析变量是否是state,主要考虑这几个问题:

  • 变量是否会通过父组件传递参数,如果是,那么它肯定不是state,这里products 肯定不是state
  • 变量是否会出现变化,如果不会出现变化,那么它肯定不是state
  • 你会不会根其他的state或者props计算这个值,如果是经过计算得到的,那么它一定不是state,由此可见filterProducts也不是state

再考虑 checkValue和 searchValue在本质上是随组件内部改变的,所以,这两个变量应该作为state管理。所以最后的程序如下

See the Pen React filter list demo by zhangmeng (@zhangmeng712) on CodePen.

3、生命周期详情分析

1) 初始化阶段
  • - getDefaultProps
  • - getInitialState
  • - propTypes
  • - Mixins
  • - statics
  • - displayName

getDefaultProps初始化参数使用,当组件类被创建的时候会被调用一次。输入为函数类型,返回object为this.props的初始化值,当父组件没有指定具体参数的时候,参数会在这个方法中被详细映射,此外 为了增加程序的可读性和容错性,建议在这个方法中指定好参数的初始值。

/*Input.jsx*/
/*Form是input的父组件*/
module.exports = React.createClass({
    displayName: 'Input',
    getDefaultProps: function () {
    return {
      //父组件中具体的参数映射,为了程序更加清晰,建议所有的props都在getDefaultProps中定义
      model: {
        name: '',
        type: '',
        value: '',
        error: false,
        enabled: true
      }
    };
  }
});

/* Form.jsx */
module.exports = React.createClass({
    displayName: 'Form',
    render: function () {
        return  (<form>
                <Input model={fieldModel}
                      ref={fieldModel.name}/>
                </form>);      
    }
})

getInitialState初始化state使用,在组件mount之前被调用一次。输入为函数,返回object为this.state的初始化值。

propTypes用于属性的验证使用,输入为object。如果输入的类型和验证中设置的类型不符,在dev环境会给出提示。除了React内置的类型如 React.PropTypes.array,还支持自定义类型,详情见Reusable Components.

var ComponentBox = React.createClass({      
        //默认值设置
        getDefaultProps: function () {
            return {
                initalX: 14,
                initalProp: '11',  //内置校验
                customProp: 'hi' //自定义校验
            }
        },
        //允许校验属性的方法,只在dev环境会显示warn
        propTypes: {
            //React内置类型
            initalProp: React.PropTypes.array,
            //自定义类型
            customProp: function (props, propName,componentName) {
                if (props[propName] !== 'hello') {
                    return new Error('Validation failed, customProp value needs to be hello');
                }
            }
        }
    });
    var compInstance = React.render(
            <ComponentBox />,
            document.getElementById('content') //DOM Element
    );

Mixins输入为array类型,用于定义组件间共享的方法,其中的方法可以是生命周期的方法,也可以是自定义方法。不过有几点需要注意:

  • 定义在其内部的方法会优先于类上的方法执行
  • 在Mixins中和类上同时定义render方法会抛出异常(Uncaught Error: Invariant Violation: ReactClassInterface: You are attempting to define `render` on your component more than once)
  • 在Mixins数组中定义同样名称的非生命周期方法也会抛出异常(Uncaught Error: Invariant Violation: ReactClassInterface: You are attempting to define `logFunc` on your component more than once. )
var commonMixins = {
        getDefaultProps: function() {
            return {
                initalY: 20
            }
        }
    };
    var commonMixins1 = {
        //定义2个render定义会抛出异常
        //        render: function () {
        //
        //        }
        // 定义同样名称的非生命周期方法也会抛出异常
        logFunc: function() {
            console.log('Mixins log method')
        }
    };
    var commonMixins2 = {
        logFunc: function() {
            console.log('Mixins log method')
        }
    };
    var ComponentBox = React.createClass({
        mixins: [commonMixins,commonMixins1,commonMixins2]
    });
    var compInstance = React.render( < ComponentBox / > ,
        document.getElementById('content') //DOM Element
    );

statics类上的静态方法,可以在实例化之前被类自己调用。

displayName用于在调试信息中标示组件,JSX会自动的设置它的值

2) Mounting阶段
  • - componentWillMount
  • - componentDidMount

componentWillMount只会被调用一次。运行于初始化之后,render方法之前。当在此函数中调用setState后,render会显示被修改的state内容,注意,尽管state内容被改变了,但是不会再多次调用render。

componentDidMount在render执行之后被调用,这个方法只会被调用一次。在这个方法中,可以通过React.findDOMNode(this)对组件的dom元素进行操作,子组件的componentDidMount方法会优先于父组件的componentDidMount方法被调用。我们会在这个方法中执行ajax请求或者调用timer或者用其他类库进行交互。

...
componentDidMount: function () {
            console.log('-------componentDidMount execute-------')
            var input = $('input[type="text"]');
            input.focus();
            var cityName = this.props.propValue;
            $.get('http://api.openweathermap.org/data/2.5/weather?q=' + cityName , function (data,status) {
                  if (status === 'success') {
                        var weather = data.weather || [];
                        if (weather.length && weather[0]) {
                            this.setProps({
                                weather: weather[0].description
                            });
                        }

                  }
            }.bind(this))

        }
3) 数据更新阶段
  • - componentWillReceiveProps
  • - shouldComponentUpdate
  • - componentWillUpdate
  • - componentDidUpdate

componentWillReceiveProps当新的props参数被发现时,就会调用这个方法,普通的render之后是不调用这个函数的。改变之前的props参数可以通过 this.props获得,新的参数可以通过第一个入参获得。在这个方法中调用setState不会触发额外的render调用。

 componentWillReceiveProps: function (nextProps) {
            console.log('-------componentWillReceiveProps execute-------', 'old weather is this.props.weather:', this.props.weather, 'new props is nextProps.weahter:' + nextProps.weather);
        }

shouldComponentUpdate当props和state变化后被触发,初次render和强制更新的时候此方法不会被调用。当你希望某个props或者state的值改变的时候,不需要render被再次执行,就可以在shouldComponentUpdate中return false来实现,而此时接下来的 componentWillUpdate 和componentDidUpdate也不会执行。默认shouldComponentUpdate会自动返回true,但是你可以通过比较参数的变化来重写这个函数,如下:

shouldComponentUpdate: function (nextProps, nextState) {
            console.log('-------shouldComponentUpdate execute-------')
            var flag = true;
            if (nextProps.weather == 'light rain' ) {
                flag = false;
            }
            return flag;
        }

componentWillUpdate判断完是否能调用render之后,就会执行componentWillUpdate,这个函数是用作render之前发生更新的改变。在这个方法中不能使用setState方法,如果要更新state,在componentWillReceiveProps函数中进行处理。

componentDidUpdate当参数变更完成,render执行完成DOM完全被更新之后会触发,可用于操作新更新的DOM元素,入参为 prevProps和 prevState,以防操作中需要变更前的数据。

4) Unmounting阶段
  • - componentWillUnmount

componentDidUpdate当组件从DOM中销毁的时候调用,可在函数中对timer和不需要的dom元素进行清理

关于组件的生命周期执行顺序和props更新流程等可以参考以下的例子:

See the Pen React lifycycle testing by zhangmeng (@zhangmeng712) on CodePen.

二、你应该掌握的API用法

1、常用 API

  • React.createClass
  • React.render
  • React.findDOMNode
  • React.createElement:创建一个virtual dom进行渲染,可以使用React.render进行加载
  • React.Children: 用于处理组件中的this.props.children
    • React.Children.map
    • React.Children.only
    • React.Children.forEach
    • React.Children.count

2、组件相关API

  • this.setState:设置state,除了支持key value的传送方式,还支持传入fn, 注意通过上述的描述可以调用它的生命周期方法为:componentDidMount以及componentWillMount(不会触发刷新render,但是可以赋值)。
  • this.setProps:设置props
  • this.props.children:表示在实例化的时候传入组件的所有子节点,可以通过React.Children来统一处理渲染到页面上
  • this.props.refs:render中Dom Node可以用ref来标示,这样就可以利用React.findDOMNode(this.refs.xx)来获取对应的DOM元素了。ref可以传入名称,也可以传入函数更多详情
//setState的函数传入,入参为之前的state对象,还有当前的props对象
 this.setState(function (prevState, currentProps) {
                                return {
                                    stateValue: 'new stateValue'
                                }
                            });


//操作this.props.children
 var ComponentBox = React.createClass({
    render: function () {
          return (<ul>
                {
                       //能够渲染 
                 React.Children.map(this.props.children, function (child) {
                            return <li>{child}</li>
                        })

                }
                {
                        //  返回不是object ?渲染不出
                        React.Children.forEach(this.props.children, function (child) {
                            return <li>{child}</li>
                        })

                }
            </ul>)
        } 
});
var compInstance = React.render(
            <ComponentBox><span>list1</span><span>list2</span></ComponentBox>,
            document.getElementById('content') //DOM Element
    );

//refs的用法
<input ref={ function(component){ React.findDOMNode(component).focus();} } />

<input type="text" ref="myTextInput" defaultValue={this.props.propValue} />
 var input = React.findDOMNode(this.refs.myTextInput)

3、Add-on API

  • Animation API:
    • ReactCSSTransitionGroup 用于控制动画的标签。
    • transitionName 定义动画的class前缀,以下例为基础默认在标签内新增的的元素,动画效果的className为example-enter example-enter-active;元素被删除时候效果的className为 example-leave example-leave-active
    • transitionAppear v0.13开始有的方法,用于书写初始化的动画效果,默认是false。注意:一定要先让ReactCSSTransitionGroup这个标签渲染出来,然后再在标签内增加元素,否则效果不生效。
    • 有人在使用这个Add-on的时候遇到了动画时序的问题,也可以使用自定义的动画解决,参考这个例子
     <ReactCSSTransitionGroup transitionName="example" transitionAppear={true}>
                        {items}
                        </ReactCSSTransitionGroup>
    

See the Pen react animtion test by zhangmeng (@zhangmeng712) on CodePen.

组件的拆分和数据的传递

一般来说,稍微复杂的组件可以被拆分成若干组件(拆分本着一个组件只做一件事情的原则,参见Single_responsibility_principle)。合理的组件拆分会让React组件的开发复用性更强,那么在组件中如何管理组件间数据的传递?具体可以参考《How to communicate between React components》这篇文章,讲解的非常详细。

参考资料

  • https://github.com/uberVU/react-guide/blob/master/props-vs-state.md
  • http://ctheu.com/2015/02/12/how-to-communicate-between-react-components/#child_to_parent
  • http://facebook.github.io/react/docs/thinking-in-react.html