本文将首先讲述如何经过React nodes建立基础的React组件,而后进一步剖析React组件内部的点滴,包括该如何理解React组件,获取React组件实例的两种办法,React事件系统,对React生命周期函数的理解,获取React组件的子组件和子节点的方法,字符串
ref
和函数式ref
,以及触发React组件从新渲染的四种方法。
本文是React启蒙系列的第六章,依旧讲的是React的基础使用方法,可是若是你对上面提到的概念有不理解或不熟悉的地方,跳到对应地方观看阅读,你应该会能有所收获。html
在具体说明如何建立React组件的语法以前,对什么是React组件,其存在的意思及其划分依据等作一个论述是颇有必要的。node
咱们设想如今有一个webApp,这个app能够用来实现不少功能,依据功能,咱们能够把其划分为多个功能碎片。要实现这么一个功能碎片,可能须要更多更小的逻辑单元,甚至还能够继续分。而咱们编程其实就是在有一个整体轮廓的前提下,经过解决一个个小小的问题来解决一个小问题,解决一个个小问题来实现软件的开发。React组件就是这样,你能够就把它当作一个个可组合的功能单元。react
以一个登录框为例,登陆框自己就是网站的一个组件,可是其内包含诸如文本输入框,登录按钮等,固然若是你想要作的只是最基础的功能,输入框和按钮等能够只是一个个React 节点,可是若是你想为输入框加上输入检测,输入框可能就有必要写成一个单独的组件了,这样也有利于复用,以后须要作的可能只是简单的经过props
传入不一样的参数就能够实现不一样的检测。假想咱们如今的登陆框组件,包含React <Button>
元素造成登陆按钮,也包含多个文本输入检测组件。那么父组件的做用一方面在于聚合小组件造成更复杂的功能单元,另外一方面在于为子组件信息的沟通提供渠道(好比说在知足必定的输入条件后,登陆按钮的状态从不可点击变为可点击)。git
React组件经过调用React.createClass()
方法建立,该方法须要传入一个对象形式的参数。在该对象中能够为所建立组件配置各类参数,其可用参数以下表:github
方法(配置参数)名称 | 描述 |
---|---|
render() | 必填,一般为一个返回React nodes或者其它组件的函数 |
getInitialState() | 一个用于设置最初的state的函数,返回一个对象 |
getDefaultProps() | 一个用于设置默认props 的函数,返回值为一个对象 |
propTypes | 一个用于验证特定props 类型的对象 |
mixins | 组件间共享方法的途径 |
statics | 一个由多个静态方法组成的对象,静态方法中不能直接调用props 和state (可经过参数) |
displayName | 是一个用于命名组件的字符串,用于展现调试信息,使用JSX时将自动设置?? |
componentWillMount() | 在组件首次渲染前触发,只会触发一次 |
componentDidMount() | 在组件首次渲染后触发,只会触发一次 |
componentWillReceiveProps() | 在组件将接受新props 时触发 |
shouldComponentUpdate() | 组件再次渲染前触发,可用于判断是否须要再次渲染 |
componentWillUpdate() | 组件再次渲染前当即触发 |
componentDidUpdate() | 组件渲染后当即触发 |
componentWillUnmount() | 组件卸载前当即触发 |
在上述因此方法中,最重要且必不可少的是render()
,它的做用是返回React节点和组件,其它全部的方法是可选的。web
实际写一个例子总比空说要容易理解,如下是使用React的React.createClass()
建立的Timer组件编程
var Timer = React.createClass({ getInitialState: function() { return { secondsElapsed: Number(this.props.startTime) || 0 }; }, tick: function() { //自定义方法 this.setState({ secondsElapsed: this.state.secondsElapsed + 1 }); }, componentDidMount: function() {//生命周期函数 this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() {//生命周期函数 clearInterval(this.interval); }, render: function() { //使用JSX返回节点 return ( <div> Seconds Elapsed: {this.state.secondsElapsed} </div> ); } }); ReactDOM.render(< Timer startTime = "60" / >, app); //pass startTime prop, used for state
如今若是对上述组件建立的代码有所疑惑也没关系,本文接下来将一步步的介绍上述代码中设计都的各个概念,包括this
,生命周期函数,React返回值的格式,如何在React中自定义函数,以及React组件中事件的定义等等。浏览器
在此须要注意的是组件名是以大写开头的。babel
当一个组件被建立(挂载)之后,咱们就可使用组件的API了,一个组件包含如下四个API
this.setState()
,
this.setState({mykey: 'my new value'}); this.setState(function(previousState, currentProps) { return {myInteger: previousState.myInteger + 1}; });
做用:
用以从新渲染组件或者子组件
replaceState()
this.replceState({mykey: 'my new value'});
做用:
效果和`setState()`相似,不过并不会和老的状态合并,而是直接删除老的状态,应用新的状态。
forceUpdate()
this.forceUpdate(function(){//callback});
做用:
调用此方法将跳过组件的`shouldComponentUpdate()`事件,直接调用`render()`
isMounted()
this.isMounted()
做用
判断组件是否被挂载在DOM中,组件被挂载返回`true`,不然返回`false`
最经常使用的组件API是setState()
,后文还会细讲。
小结
componentWillUnmount
, componentDidUpdate
, componentWillUpdate
, shouldComponentUpdate
, componentWillReceiveProps
, componentDidMount
, componentWillMount
等方法被称做React 组件的生命周期函数,它们会在组件生命过程的不一样阶段被触发。
React.createClass()
是一个方便的建立组件实例的方法;
render()
方法应该保持纯洁;
render()
方法中不能更改组件状态
上文已经提到每一个React组件必须有的方法就是render()
,这个方法的返回值只能是一个react 节点或一个react组件,这个节点或组件中能够包含任意多的子节点或者子元素。在下面的例子中咱们能够看到在<reactNode>
中包含了多个子节点。
var MyComponent = React.createClass({ render: function() { return <reactNode> <span>test</span> <span>test</span> </reactNode>; } }); ReactDOM.render(<MyComponent />, app);
值得注意的地方在于,若是你想返回的react 节点超过一行,应该用括号把返回值包围起来,以下所示
var MyComponent = React.createClass({ render: function() { return ( <reactNode> <span>test</span> <span>test</span> </reactNode> ); } }); ReactDOM.render(<MyComponent />, app);
另外一个值得注意的地方是返回值最外层不能出现多个节点(组件),否者会报错
var MyComponent = React.createClass({ render: function() { return ( <span>test</span> <span>test</span> ); } }); ReactDOM.render(<MyComponent />, app);
上述代码就会报错,报错信息以下
babel.js:62789 Uncaught SyntaxError: embedded: Adjacent JSX elements must be wrapped in an enclosing tag (10:3) 8 | return ( 9 | <span>test</span> > 10 | <span>test</span> | ^ 11 | ); 12 | } 13 | });
通常来讲开发者会在最外层加上一个<div>
元素包裹其它节点以免此类错误。
一样,若是return()
中的最外层出现了多个组件,也会出错。
当一个组件被render
后,一个组件便经过传入的参数实例化了,咱们有两种办法获取这个实例及其内部属性(this.props
和this.setState()
)。
第一种方法就是使用this
关键字,在组件内部的方法中使用this
咱们发现,这个this
指向的就是该组件实例。
var Foo = React.createClass({ componentWillMount:function(){ console.log(this) }, componentDidMount:function(){ console.log(this) }, render: function() { return <div>{console.log(this)}</div>; } }); ReactDOM.render(<Foo />, document.getElementById('app'));
获取某组件实例的另一种方法是调用ReactDOM.render()
方法,这个方法的返回值是最外层的组件实例。
看以下代码能够更好的理解这句话
var Bar = React.createClass({ render: function() { return <div></div>; } }); var foo; //store a reference to the instance outside of function var Foo = React.createClass({ render: function() { return <Bar>{foo = this}</Bar>; } }); var FooInstance = ReactDOM.render(<Foo />, document.getElementById('app')); console.log(FooInstance === foo); //true,说明返回值和指向一致
小结this
的最多见用法就是在一个组件内调用该组件的各个属性和方法,如this.props.[NAME OF PROP]
,this.props.children
,this.state
,this.setState()
,this.replaceState()
等。
第四章和第五章已经屡次介绍过React的事件系统,事件能够被直接添加都React节点上,下面的代码示例中,咱们添加了两个React事件(onClick
&onMouseOver
)到React<div>
节点中
var MyComponent = React.createClass({ mouseOverHandler:function mouseOverHandler(e) { console.log('you moused over'); console.log(e); //e is sysnthetic event instance }, clickHandler:function clickhandler(e) { console.log('you clicked'); console.log(e); //e is sysnthetic event instance }, render:function(){ return ( <div onClick={this.clickHandler} onMouseOver={this.mouseOverHandler}>click or mouse over</div> ) } }); ReactDOM.render(<MyComponent />, document.getElementById('app'));
事件能够被看作是特殊的props
,只是React对这些特殊的props
的处理方式和普通的props
有所不一样。
这种不一样表如今会自动为事件的回调函数绑定上下文,在下面的示例中,回调函数中的this
指向了组件实例自己。
var MyComponent = React.createClass({ mouseOverHandler:function mouseOverHandler(e) { console.log(this); //this is component instance console.log(e); //e is sysnthetic event instance }, render:function(){ return ( <div onMouseOver={this.mouseOverHandler}>mouse over me</div> ) } }); ReactDOM.render(<MyComponent />, document.getElementById('app'));
React所支持的因此事件可见此表
小结
React规范化了事件在不一样浏览器中的表现,你能够放心的跨浏览器使用;
React事件默认在事件冒泡阶段(bubbling)触发,若是想在事件捕获阶段触发须要在事件名后加上Capture
(如onClick
变为onClickCapture
);
若是你想获知浏览器事件的详情,你能够经过在回调函数中查看SyntheticEvent
对象中的nativeEvent
值;
React实际上并未直接为React nodes添加事件,它使用的是event delegation事件委托机制
想要阻止事件冒泡,须要手动调用e.stopPropagation()
或e.preventDefault()
,不要直接使用returning false
,
React其实并无支持全部的JS事件,不过它还提供额外的生命周期函数以供使用React lifecycle methods.
React组件的render()
方法中能够包含对其它组件的引用,这使得组件之间能够嵌套,通常咱们把被嵌套的组件称为嵌套组件的子组件。
下例中组件BadgeList包含了BadgeBill和BadgeTom两个组件。
var BadgeBill = React.createClass({ render: function() {return <div>Bill</div>;} }); var BadgeTom = React.createClass({ render: function() {return <div>Tom</div>;} }); var BadgeList = React.createClass({ render: function() { return (<div> <BadgeBill/> <BadgeTom /> </div>); } }); ReactDOM.render(<BadgeList />, document.getElementById('app'));
此处为展现嵌套关系,代码有所简化。
小结
编写可维护性UI的关键之一在于可组合组件,React组件自然适用这一原理;
在render
方法中,组件和HTML
能够组合使用;
每一个组件都具备一系列的发生在其生命中不一样阶段的事件,这些事件被称为生命周期函数。
生命周期函数能够理解为React为组件的不一样阶段提供了的钩子函数,用以更好的操做组件,下例是一个定时器组件,其在不一样生命周期函数中执行了不一样的事件
var Timer = React.createClass({ getInitialState: function() { console.log('getInitialState lifecycle method ran!'); return {secondsElapsed: Number(this.props.startTime) || 0}; }, tick: function() { console.log(ReactDOM.findDOMNode(this)); if(this.state.secondsElapsed === 65){ ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this).parentNode); return; } this.setState({secondsElapsed: this.state.secondsElapsed + 1}); }, componentDidMount: function() { console.log('componentDidMount lifecycle method ran!'); this.interval = setInterval(this.tick, 1000); }, componentWillUnmount: function() { console.log('componentWillUnmount lifecycle method ran!'); clearInterval(this.interval); }, render: function() { return (<div>Seconds Elapsed: {this.state.secondsElapsed}</div>); } }); ReactDOM.render(< Timer startTime = "60" / >, app);
组件的生命周期可被分为挂载(Mounting),更新(Updating)和卸载(UnMounting)三个阶段。
下面将对不一样阶段各函数的功能及用途进行描述,弄清这一点很重要
挂载阶段
这是React组件生命周期的第一个阶段,也能够称为组件出生阶段,这个阶段组件被初始化,得到初始的
props
并定义将会用到的state
,此阶段结束时,组件及其子元素都会在UI中被渲染(DOM,UIview等),咱们还能够对渲染后的组件进行进一步的加工。这个阶段的全部方法在组件生命中只会被触发一次。React-in-depth
对挂载阶段的生命周期函数的描述
| 方法 | 描述 |
| --------- | ------ |
|getInitialState()
| 在组件挂载前被触发,富状态组件应该调用此方法以得到初始的状态值 |
|componentWillMount()
|在组件挂载前被触发,富状态组件应该调用此方法以得到初始的状态值|
|componentDidMount()
| 组件被挂载后当即触发,在此能够对DOM进行操做了 |
更新阶段
这个阶段的函数会在组件的整个生命周期中不断被触发,这是组件一辈子中最长的时期。这个阶段的函数能够得到新的
props
,能够更改state
,能够对用户的交互进行反应。React-in-depth
对更新阶段的生命周期函数的描述
方法 | 描述 |
---|---|
componentWillReceiveProps(object nextProps) |
在组件接受新的props 时被触发,能够用来比较新老props ,并使用this.setState() 来改变组件状态 |
shouldComponentUpdate(object nextProps, object nextState) |
此组件能够对比新老props 和state ,用以确认该组件是否须要从新渲染,若是返回值为false ,将跳过这次渲染,此方法经常使用于优化React性能 |
componentWillUpdate(object nextProps, object nextState) |
在组件从新渲染前被触发,此时不能再调用this.setState() 对state 进行更改 |
componentDidUpdate(object prevProps, object prevState) |
在从新渲染后当即被触发,此时可调用新的DOM了 |
卸载阶段
这是组件生命的最后一个阶段,也能够被称为是组件的死亡阶段,此阶段对应组件从Native UI中卸载之时,具体说来多是用户切换了页面,或者页面改变去除了某个组件,卸载阶段的函数只会被触发一次,而后该组件就会被加入浏览器的垃圾回收机制。React-in-depth
对此阶段的生命周期函数的描述
方法 | 描述 |
---|---|
componentWillUnmount() |
组件卸载前当即被触发,此阶段经常使用来执行一些清理工做(好比说清除setInterval ) |
小结
componentDidMount
和 componentDidUpdate
经常使用来加载第三方的库(此时真实DOM存在,可加载各类图表库)。
组件挂载阶段的各事件执行顺序以下
Initialize / Construction
获取初始的props,ES5中使用 getDefaultProps()
(React.createClass),ES6中使用 MyComponent.defaultProps
(ES6 class)
初始组件的state
值,ES5中使用getInitialState()
(React.createClass) ,ES6中使用 this.state = ...
(ES6 constructor)
componentWillMount()
render()
第一次渲染
Children initialization & life cycle kickoff,子组件重复上述(1~5步)过程;
componentDidMount()
经过上面的过程分析,咱们能够知道,在父元素执行
componentDidMount()
时,子元素和子组件都已经存在于真实DOM中了,所以在此能够放心调用。
组件更新阶段各函数执行顺序以下
componentWillReceiveProps()
:比较新老props
,对state
进行改变;
shouldComponentUpdate()
:判断组件是否须要从新渲染
render()
:从新渲染
Children Life cycle methods
:子元素重复上述过程
componentWillUpdate()
:此阶段能够调用新的DOM了
组件卸载阶段各函数执行顺序以下
componentWillUnmount()
Children Life cycle methods:触发子元素的生命周期函数,也将被卸载
被浏览器从内存中清除;
若是一个组件包含子组件或React节点(如<Parent><Child /></Parent>
或 <Parent><span>test<span></Parent>
),这些子节点和子组件能够经过React的this.props.children
的方法来获取。
下面的例子展现了如何使用this.props.children
var Parent2 = React.createClass({ componentDidMount: function() { //将会得到<span>child2text</span>, console.log(this.props.children); //将会得到 child2text, 或得了子元素<span>的子元素 console.log(this.props.children.props.children); }, render: function() {return <div />;} }); var Parent = React.createClass({ componentDidMount: function() { //得到了一个数组 <div>test</div> <div>test</div> console.log(this.props.children); //得到了这个数组中的对应子元素中的子元素 childtext, console.log(this.props.children[1].props.children); }, render: function() {return <Parent2><span>child2text</span></Parent2>;} }); ReactDOM.render( <Parent><div>child</div><div>childtext</div></Parent>, document.getElementById('app') );
观察上述的代码能够看出如下几点
Parent组件实例的this.props.children
获取到由直系子元素组成的数组,能够对子元素套用此方法得到子元素(组件)的子元素(组件)(this.props.children[1].props.children
);
子元素指的是由该实例围起来的元素,而非该实例内部元素;
为了更好的操做this.props.children
包含的是一组元素,React还提供了如下方法
方法 | 描述 |
---|---|
React.Children.map(this.props.children, function(){}) |
在每个直接子级(包含在 children 参数中的)上调用 fn 函数,此函数中的 this 指向 上下文。若是 children 是一个内嵌的对象或者数组,它将被遍历,每一个键值对都会添加到新的 Map。若是 children 参数是 null 或者 undefined,那么返回 null 或者 undefined 而不是一个空对象。 |
React.Children.forEach(this.props.children, function(){}) |
相似于Children.map() 可是不会反回数组 |
React.Children.count(this.props.children) |
返回组件子元素的总数量,其数目等于Children.map() 和Children.forEach() 的执行次数。 |
React.Children.only(this.props.children) |
返回惟一的子元素不然报错 |
React.Children.toArray(this.props.children) |
返回一个由各子元素组成的数组,若是你想在render事件中操做子元素的集合时,这个方法特别有用,尤为是在从新排序或分割子元素时 |
小结
当只有一个子元素时,this.props.children
之间返回该子元素,不会用一个数组包裹着该子元素;
须要注意的是children
并不是某组件内部的节点,而是由该组件包裹的组件或节点‘
ref
ref
属性使得咱们获取了对某一个React节点或某一个子组件的引用,这个在你须要直接操做DOM时很是有用。
字符串ref
的使用很简单,可分为两步:
一是给你想引用的的子元素或组件添加ref
属性,
而后在本组件中经过this.refs.value(你所设置的属性名)
便可引用;
不过还存在一种函数式的ref,看下面的例子
var C2 = React.createClass({ render: function() {return <span ref={function(span) {console.log(span)}} />} }); var C1 = React.createClass({ render: function() {return( <div> <C2 ref={function(c2) {console.log(c2)}}></C2> <div ref={function(div) {console.log(div)}}></div> </div>)} }); ReactDOM.render(<C1 ref={function(ci) {console.log(ci)}} />,document.getElementById('app'));
上述例子的console结果都是指向ref所在的组件或元素,经过console的结果咱们也能够发现,打印结果说明其指向的是真实的HTML DOM而非Virtual DOM。
若是不想用字符串ref
,经过下面的方法也能够引用到你想引用的节点
var MyComponent = React.createClass({ handleClick: function() { // focus()对真实DOM元素有效 this.textInput.focus(); }, render: function() { // ref中传入了一个回调函数,把该节点自己赋值给this.input return ( <div> <input type="text" ref={(thisInput) => {this.textInput = thisInput}} /> <input type="button" value="Focus the text input" onClick={this.handleClick} /> </div> ); } }); ReactDOM.render( <MyComponent />, document.getElementById('app') );
小结
对无状态函数式组件不能使用ref
,由于这种组件并不会返回一个实例;
ref有两种,字符串ref和函数式ref,不过字符串ref(经过refs调用这种)在将来可能被放弃,函数式ref是趋势;
组件有ref,能够经过ref调用该组件内部的方法;
使用行内函数表达式使用ref意味着每次更新React都会将其视为一个不一样的函数对象,ref中的函数会以null为参数被当即执行(和在实例中调用不冲突)。好比说,当ref所指向的对象被卸载时,或者ref改变时,老的的ref函数都会以null为参数被调用。
对应ref的使用,React官方有两点建议:
ref容许你直接操做节点,这一点有些状况下是很是方便的,不过须要注意的是,若是能够经过更改state
来达到你想要的效果,那就不要随便使用ref啦;
若是你刚刚接触React,在你想用ref的时候,仍是尽可能多思考一下看能不能用state
来解决,仔细思考你会发现,state
能够解决大部分操做问题的,比较直接操做DOM并未React的初衷。
咱们已经接触了ReactDOM.render()
方法,这个方法使得组件及其子组件被初始化渲染。在此次渲染以后,React为咱们提供了两种方法来从新渲染某个组件
在组件内调用setState()
方法;
在组件中调用fouceUpdate()
方法;
每当一个组件被从新渲染时,其子组件也会被从新渲染(在Virtual DOM中发生,在真实DOM中表现出来)。不过须要注意的是Virtual DOM的改变并非必定在真实DOM中就会有所表现。
在下面的例子中,ReactDOM.render(< App / >, app)
初始化渲染了<App/>
及其子组件<Timer/>
,接下来的<App/>
中的setInterval()
事件调用this.setState()
导致两个组件被从新渲染。在5秒后,setInterval()
被清除,而在十秒后this.forceUpdate()
被触发又使得页面被从新渲染。
var Timer = React.createClass({ render: function() { return ( <div>{this.props.now}</div> ) } }); var App = React.createClass({ getInitialState: function() { return {now: Date.now()}; }, componentDidMount: function() { var foo = setInterval(function() { this.setState({now: Date.now()}); }.bind(this), 1000); setTimeout(function(){ clearInterval(foo); }, 5000); //DON'T DO THIS, JUST DEMONSTRATING .forceUpdate() setTimeout(function(){ this.state.now = 'foo'; this.forceUpdate() }.bind(this), 10000); }, render: function() { return ( <Timer now={this.state.now}></Timer> ) } }); ReactDOM.render(< App / >, app);
从开始翻译本书到如今已有一个多月,基础的翻译工做终于算是告一段落。
《React Enlightenment》的第七章和第八章讲述的是React的props
和state
已由@linda102翻译完成。
在大概一个多月前看到本书原文时,我已经用了快五个月React,可是看完本书仍是挺有收获。
翻译本书的初衷有两点,一是增强本身对React基础的理解,二是回想起,我在初学React时曾购买过国内的一本关于React的基础书籍,价格是四十多,可是其实看完并未有太多收获,该书大多就是翻译的官方文档,并且翻译的也不全面,并不那么容易理解,因此但愿这篇译文对初学者友好,让初学者少走弯路。
因为翻译时间和水平都有限,译文内部不可避免存在一些不恰当的地方,若是您在阅读的过程当中有好的建议,请直接提出,我会尽快修改。谢谢