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

css3和动画-part1-变换

常年开发web后台系统,实在厌恶了做不完的需求、调不完的接口、和各种数据交互,所以闲暇之余开始了动画和游戏的学习,也算一种调剂。动画比游戏应用更为广泛,所以我们先从动画说起,这系列教程主要包括如下几篇文章:

  • css3和动画-part1 基础篇;css3动画基础和实例(有demo有真相 力求比w3cschool实用些)
  • css3和动画-part2 进阶篇;理论结合实际实现几个较为复杂但有意思的动画效果
  • css3和动画-part3 蛋疼篇;transform、matrix和贝塞尔曲线
  • javascript和动画-part1 姊妹篇;使用javascript完成动画
  • javascript和动画-part2 姊妹进阶篇;高性能js动画类库分析-snabbt和gsap

废话不说(此处省略XXXX个字),上来先总结一下动画的相关的知识,让大家有个大概的印象,然后再各个击破。css3动画有很多名词都比较相似,例如 transform transiton translate,参数用法也特别容易混,这里我主要按照我学习的线索作为提纲分块讲解,希望对大家有所帮助:

  • 实现各种变换效果的关键字:transform: translate opacity skew rotate…
  • 静到动的实现函数:transition和animation
  • 3D相关:translate3d perspective

变换

不谈代码,我们大概能想到的变换效果都有哪些呢?放大、缩小、旋转、显隐,上下左右移动, 扭曲等等。对于这些变换,css3为给我们提供了丰富的效果关键字,这篇文章主要谈论常用的、高效的变换。之所以说是高效的变换,其实主要是在渲染的过程中能够尽量减少浏览器Recalculate的变换效果,参见High Performance Animations。这些变换有:

  • Position:translate
  • Scale
  • Rotate
  • opacity

transform

这个是个非常重要的关键字,他的作用是,transform:需要变换的属性 不同属性直接用空格分隔,举例来说要实现 某一文字放大并向右平移50像素,代码为:

    -webkit-transform: scale(1.1) translate(50px, 0);
    -ms-transform: scale(1.1) translate(50px, 0);
    transform: scale(1.1) translate(50px, 0)

下面就具体讲讲,被transform的各种变换效果。

translate

如果要实现元素从某一位置变换到另一个位置的时候,我们可以使用top left进行变换, 也可以使用margin-left margin-top,但是,其实这些都是会触发浏览器的Recalculate,会让动画出现卡顿,实现效率也非常的低下,参考jsperf做的对比,也可以戳戳Paul Irish的文章。如果改用translate这个关键字就可以非常cheaply的实现动画效果。translate主要是用来改变元素坐标的一种变换。API为:

  • transform: translate(tx[, ty]) /* one or two
    values */
  • transform: translateX(tx)
  • transform: translateY(ty)

常见的场景就是使用translate实现轮播的效果,下面是轮播的最简单的一个demo实现:

See the Pen translate by zhangmeng (@zhangmeng712) on CodePen.


最核心的代码就是:

.card-list.second {
  -webkit-transform: translate(-540px, 0px);
  -ms-transform: translate(-540px, 0px);
  transform: translate(-540px, 0px);
}
	

Tips: 我们不是电脑,没法记得哪些需要前缀哪些不需要,这里推荐给大家一个工具,autoprefixer这个是在caniuse这个网站的基础上用来帮我们实现兼容的前缀的,我们可以只写最基本的,他自动会帮我们补全,在线地址

scale

scale如同它的英文含义一样,是用来把元素进行放大缩小的,它也是非常实用的,先看API:

  • transform: scale(sx[, sy]);
  • transform: scaleX(sx);
  • transform: scaleY(sy);

scale最常见的应用就是在鼠标hover的时候把图标进行放大,以表示选中状态,引起注意,如下面这个demo,这里也有个工具网站推荐shapeofcss,在这里面可以找到我们常见的形状的实现。例子中同时使用了webfontIcon,这个技术让前端彻底告别切图的时代。

See the Pen scale demo by zhangmeng (@zhangmeng712) on CodePen.

rotate

rotate是用于让元素旋转的效果。旋转效果常配合着3D的效果一起使用,实现空间化的效果;也经常配合animation实现loading spinner的效果,如下demo:

  • transform: rotate(angle); /* an , e.g., rotate(30deg) */
  • transform: rotateX(angle);
  • transform: rotateY(angle);

See the Pen simple loading spinner by zhangmeng (@zhangmeng712) on CodePen.

opacity

opacity的用途其实更加的广泛,不管是rotate也好scale也好,如果没有一些opacity的变化就会让变换十分的生硬。配合opacity和display的切换是会导致重排的。所以如果需要元素的隐藏显示,最好采用opacity来控制。opacity还常常和animation配合起来,用于实现fadeIn fadeOut等人性化的动画设计。注意 opacity虽然是一种效果,但是并不需要使用transform来控制。API非常简单: opacity:0.4,记得每次开一个新的项目都会有实现transparent全兼容的css的代码,在此也为大家提供一下,见CssTricks

.transparent_class {
  /* IE 8 */
  -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=50)";

  /* IE 5-7 */
  filter: alpha(opacity=50);

  /* Netscape */
  -moz-opacity: 0.5;

  /* Safari 1.x */
  -khtml-opacity: 0.5;

  /* Good browsers */
  opacity: 0.5;
}

本来想一篇文章把“变换” “动画” 还有“3D效果”一起讲解,但发现要说的东西太多了,为了保证质量,拆分成了三篇,力求把动画的内容涵盖完全,敬请期待哦~

参考: