对于初学React的同学而言,这并不是一件易事。就拿我自己来说,都不知道从何下手,应该如何去学习才能开始使用React。就算你对React不陌生,学习React也常会碰到一些瓶颈。比如说新颖的概念、开发工具的使用、抽象的名词、快速变化的生态环境等等。也就是说,一旦开始学习React,你会发觉要学的东西越来越多,甚至可能还没开始碰到React就被这些东西给吓跑了(特别是对于初学者,听到这些东东就傻眼了)。
这篇文章不是来介绍怎么学习React,而是要让初学者或没学过React的同学对React的一些重要概念有所了解。
Element对于我们来说并不陌生,中文常称之为 元素 ,也就是HTML里的标签元素。比如:
<h1>W3cplus</h1> <p>Des...</p>
这里的 <h1>
、 <p>
等等就是元素。这些元素都可以添加一些属性,比如说 class
、 id
、 style
以及一些自定义的属性 data-*
。这我们所说的Element是HTML,对于我们来说很好理解,但是...
React的Element是写在JavaScript的普通对象(Plain Object)
在理解这句话之前,先来看看下面的示例,就算你是初次接触React,也应该能猜到这是一个导航元素,而且其字体的颜色是 blue
。
var app = <Nav color="blue" />
这看起来像是HTML的语法,其官方的叫法是 JSX ,和我们所了解的HTML是完全不同的。这个 <Nav color="blue" />
不是HTML的DOM节点,而是一个对象(Plain Object)。
在React中,其实要我们用物件去描述Element。下面的示例用来阐述React如何用物件描述一个按钮元素(button element),可以把 type
想成一般的 tag
名称、 props
想成是写在 tag
里的属性( attributes
)。
{ type:"button", props: { className: "btn btn-primary", children: { type: "b", props: { children: "OK!" } } } }
但这种编码方式降低了其可读性,所以我们把它包装成像我们熟悉的HTML的语法(也就是JSX),但千万记得,不能别一般的HTML搞混了,底下这段JSX实质上是一个对象。
<button className="btn btn-primary"> <b>OK!</b> </button>
在当今的前端开发上,常常需要大量的操作DOM,没处理好的话,效率是非常低的。这也就是React不希望我们这样做。
React要我们用物件去描述Element,然后它自动帮我们处理DOM,这样的开发方式叫作声明式编程(Declarative Programming),也就是React说的 What (描述想呈现的Elemnt),而不是说的 How (要怎么去处理DOM)。
如此一来,React开发上主要工作是处理JavaScript的物件,这除了比操作DOM来得更轻量且方便之外,这也是JavaScript常做的事情。也就是说 学习React其实也就是在学习JavaScript,就是处理描述Element的物件 (如果学过React的同学,就知道这就是React中的 state
)。
前面提到过了,React的ELement虽然看上去像HTML,其实它的专业术语称之为JSX。那么在学习React之前,很有必要要了解一下JSX。
JSX可以看做是JavaScript的拓展,有点类似于XML。使用React可以进行JSX语法到JavaScript的转换。它不是新语言,也没不用改变JavaScript的语法,只是对JavaScript的拓展。当然,使用React开发并不一定非要使用JSX( 感兴趣的可以看看这篇译文 )。
前面说过,JSX语法跟XML或者HTML很相似,但有一些细节需要注意:
{}
包裹起来,不要用引号 ""
{}
包围要注释的部分即可 class
、 for
这样的不建议作为XML属性名,作为替代,React DOM使用 className
和 htmlFor
来做对应的属性 上面清单所提到的一些示例:
// JSX内部写CSS格式 React.render( <button color="blue"> <b style={color:"white",display: "block"}></b> </button> ); // HTML转义 var content = "<strong>content</strong>>"; React.render( <div dangerouslySetInnerHtml = {{__html:content}}></div>, document.body ); // HTML不转义 var content = "<strong>content</strong>"; React.render( <div>{content}</div>, document.body );
咱们一起再来看看一个完整的示例:
var TopTitle = React.createClass({ getDefaultProps: function(){ return { titleContent: "TopTitleBar" }; }, render: function() { return ( <h1 id="topTitle" className="J_title">{this.props.titleContent}</h1> ); } });
在React中有两种类型的数据 模型 : props
和 state
。那么这两个概念对于学习React也很重要。
this.props
React是通过内置虚拟DOM来操作,这个组件的输入被称为 props
(也就是properties缩写)。通过JSX语法进行参数传递。在组件中,这些属性是不可以直接改变的,也就是说 this.props
是只读的。
this.state
React把用户界面当作简单的状态机。把用户界面想像成拥有不同状态,之后渲染这些状态,这样可以轻松让用户界面和数据保持一致。
React里,只需要更新组件的 state
,然后根据新的 state
重新渲染用户界面(不要操作DOM)。React来决定如何最高效的更新DOM。
在React组件内部,通过 this.state
来获取 state
值。但在使用的过程中千万要注意,不要尝试使用 this.state.xxx = "xxx"
来强制修改 state
的值,这样将会引起一系列错误。就算需要修改 state
值,在React中是提供了一些操作 state
的方法:
setState()
setState()
不会立即改变 this.state
,而是创建一个即将处理的 state
转变。在调用该方法之后获取 this.state
的值可能会得到现有的值,而不是最新设置的值。
setState()
总是触发一次重绘,除非 shouldComponentUpdate()
中实现了条件渲染逻辑。如果使用可变的对象,但又不能在 shouldComponentUpdate()
中实现这种逻辑,仅在新 state
和之前的 state
存在差异的时候调用 setState()
可避免不必要的重新渲染。
replaceState()
类似于 setState()
,只不它只是负责删除已经存在的 state
键。
render
render()
方法是必须的。顾名思义,它负责组件的渲染工作。当 render
被调用时,会去检测 this.props
和 this.state
,并且返回一个元素(这个元素可以使原生的HTML Dom也可以是React Dom,或是你自己定义的一些复合的组件模块,当然也可以返回 null
和 false
来表明你不希望做任何渲染工作)。每次被调用时, render
方法都会返回'相同'的结果,它不会去读、写DOM或是和浏览器做交互(例如使用 setTimeout
)。
注意:切记要保证 render
方法的洁净,不可以在 render
方法中修改组件的 state
。如果你希望和浏览器交互或者做更多事情,请在 componentDidMount()
方法或者其他生命周期的方法中去实现。
知道React中Element是什么,那了解React中的Component(组件)也就很好理解了。在React中是通过Element去描述Component,其本质上也可以说Component是Element。
React中的Component有两大特色:
下在是使用ES6的Arrow Function写的一个DeleteAccount组件:
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" } }] } });
从 type
的描述可以知道其本身是一个 div
元素, props
告诉他的子组件(children component)包含 p
元素及 DangerButton
和 Button
组件(前面提到过, type
并不仅是一般的 tag
标签,也可以是一个React组件)。
当然,也可以使用JSX来提高React组件的可读性:
const DeleteAccount = () => ( <div> <p>Are you sure?</p> <DangerButton>Yep</DangerButton> <Button color="blue">Cancel</Button> </div> );
这里不是要告诉大家怎么学习React的语法,而是要告诉大家学习React最重要的事情: 使用对象(Plain Object)去描述React的组件(Component)或元素(Element) 。
当我们调用 React.createClass()
方法创建一个组件类时,必须提供一个包含 render
方法的对象作为实参,当然也可以包含其他一些生命周期方法,不过他们是可选的。下面让我们按照生命周期的顺序,来依次介绍一下这些生命周期方法。
简单了解一下React组件的生命周期。React的组件生命周期,就如同人一样,也有生老病死,其大致可以分为七个周期:
state
已经被更新,并且重新渲染了,所以回到第 2
个周期 除了知道React组件的生命周期之外,还需要了解生命周期相关的方法。
在实际开发当中,那又要如何使用这些方法呢?其实在开发中,可以直接在 class
中使用这些方法,这样你就可以拦截到组件的变化,并且会执行后面相应的动作,如:
var Demo = React.createClass ({ getInitialState: function () { return { name: "W3cplus" }; }, getDefaultProps: function () { return { myName: "大漠" }; }, statics: { isUndefined: function (str) { return str === undefined; }, isNumber: function (num) { return typeof(num) === "number"; } }, propTypes: { myName: React.PropTypes.string }, componentWillReceiveProps: function () { console.log("组件收到props!"); }, componentDidUpdate: function () { console.log("render渲染更新完成!"); }, componentWillUpdate: function () { console.log("收到新的props或者state!"); }, shouldComponentUpdate: function () { console.log("收到新的props或者state!即将进行render方法"); }, componentWillMount: function () { console.log("初始化render执行前!"); }, componentDidMount: function () { console.log("初始化render渲染完成!"); }, componentWillUnmount: function () { console.log("DOM被移除"); }, render: function () { console.log("渲染开始!"); return ( <div className="container"> <h1>Hello {this.state.name}</h1> <h2>I'm {this.state.myName}</h2> </div> ); } });
比如下面这个组件示例:
class Counter extends React.Component { constructor(props, context) { super(props, context); this.state = { value: 0 }; } componentWillMount() { console.log('1. Mounting: componentWillMount'); } componentDidMount() { console.log('3. Mounted: componentDidMount'); } componentWillReceiveProps(nextProps, nextContext) { console.log('4. Recieving Props: componentWillReceiveProps'); } shouldComponentUpdate(nextProps, nextState, nextContext) { console.log('5. Recieving State: shouldComponentUpdate'); return true; } componentWillUpdate(nextProps, nextState, nextContext) { console.log('5. Recieving State: componentWillUpdate'); } componentDidUpdate(prevProps, prevState, prevContext) { console.log('3. Mounted: componentDidUpdate'); } componentWillUnmount() { console.log('5. Recieving State: componentWillUpdate'); } render() { console.log('render'); return (<h1>{this.state.value}</h1>); } }
下面简单介绍了这些方法的使用,如果你想了解更详细的信息,可以阅读 官方文件 。
this.state
this.state
的初始值 this.props
的初始值 props
中的某个键,那么 getDefaultProps()
的返回对象中的相应属性将会合并到 this.props
中作为初始值。也就是说,当父集没有传入 props
时, getDefaultProps()
方法可以保证 this.props.xxx
有默认值,并且它的返回值将被缓存,我们可以直接使用 props
而不必重复编写一些无意义的代码 this.props
,其返回值将在全部实例中共享 propTypes
用于验证传入到组件的 props
,以此来判断传入数据的有效性。当向 props
中传入无效数据时,JavaScript控制台会抛出警告 React.PropTypes
验证器支持下面这些类型。但为了保证性能,建议只在开发环境下验证 propTypes
React.PropTypes
验证器支持的类型:
PropTypes : { 'arrayKey' : React.PropTypes.array , // 数组; 'boolKey' : React.PropTypes.bool , // 布尔值; 'funcKey' : React.PropTypes.func , // 函数; 'numberKey' : React.PropTypes.number , // 数字; 'objKey' : React.PropTypes.object , // 对象; 'stringKey' : React.PropTypes.string , // 字符串; 'nodeKey' : React.PropTypes.node , //所有可以被渲染的对象(数字、字符串、DOM及包含上述三种类型的数组) 'eleKey' : React.PropTypes.element , // React元素; 'arrayOfKey' : React.PropTypes.arrayOf(React.PropTypes.number) , // 指定类型构成的数组 'oneOfKey' : React.PropTypes.oneOf(['one','two']) , // 用来限制oneOfKey的值只能接受指定值 'objWithShap' : React.PropTypes.shape({ color : React.PropTypes.string, width : React.PropTypes.number }), // 用于验证特定形状的参数对象 ... // 以上这些prop在默认情况下都是可以忽略的。 // 当在任意类型后面加上'isRequired',来使该prop不可为空。如: 'appId' : React.PropTypes.number.isRequired, 'anyTypeKey' : React.PropTypes.any.isRequired, // 用于验证不可为空的任意类型 'customProp' : function(props, propName, componentName) { if (!/matchme/.test(props[propName])) { return new Error('认证失败') } } }
mixins
数组可以让我们在多个组件间复用一些方法。大致等同于将 mixins
数组中的组件方法引入到 this
下,以便我们可以直接调用 mixin
,并且有多个 mixin
定义了同样的生命周期方法,所有这些生命周期方法都保证会被执行到。其执行顺序是:先按照 mixin
引入顺序执行 mixin
里面的方法,最后执行组件内定义的方法 statics
对象允许我们定义一些能够被组件类调用的静态方法 props
和 state
,但是却可以接受参数 setState
来改变 this.state
的值,更新后的 this.state
值将被 render
方法感知到,并且 render
只会执行一次,尽管 state
改变了 render
渲染方法之后,立即调用一次,仅客户端有效(服务端不会调用) this.getDOMNode()
来获取相应的DOM节点。我们可以在这个方法中与其他JavaScript框架进行集成、处理一些渲染后的逻辑(比如说绑定一些事件等)、发送Ajax请求或是设置定时器方法( setTimeout/setInterval
)等 props
时调用,初始化渲染时不会被调用 prop
传入之后, render()
方法执行之前更新 state
的合适时机。老的 props
可以通过 this.props
获取到 this.setState()
props
或者 state
,将要渲染之前调用 props
或 state
不需要导致组件更新时,应在此方法中 return false;
,也就是说当 shouldComponentUpdate()
返回 false
时, render()
方法不会被执行( componentWillUpdate()
、 componentDidUpdate()
都将不会被调动),直到下一次 state
改变。默认情况下, shouldComponentUpdate()
总是返回 true
forceUpdate()
时也不会被调用 render
之后 componentDidMount()
,唯一不同在于首次初始化 render
渲染完成后将执行 componentDidMount()
方法,而之后每次渲染完成都会执行 componentDidUpdate()
方法。我们可以使用 this.getDOMNode()
来访问DOM节点 componentDidMount()
中创建的DOM元素等 对于React组件的生命周期来说,我们需要了解的是:
当我们开发Web网站或应用时,前端架构变得越来越复杂,有大量的 DOM需要处理的状况之下,性能就开始会出现问题。使用React则可以帮助我们省下处理DOM的麻烦,因为使用React的话,从头到尾我们都没有必要去操作真正的DOM,我们要做的事情仅仅是处理物件(描述Element、Component的物件)。
因为React生态系统过于庞大,需要学习的东西越来越多,但要记住一件很重要的事情, 学习React其实也就是学习JavaScript 。同时使用React开发会使用到最基本的JavaScript及Web API(XHR或Fetch),当然如果你想快速完成一个案例或应用,那么React是一个很好的选择。
另外,React还有很多有趣的东西值得讨论和学习,而这篇文章只是涉及了React最基础的部分,也向大家阐述了学习React最重要的一件事情: React是处理物件而不是处理DOM 。
常用昵称“大漠”,W3CPlus创始人,目前就职于手淘。对HTML5、CSS3和Sass等前端脚本语言有非常深入的认识和丰富的实践经验,尤其专注对CSS3的研究,是国内最早研究和使用CSS3技术的一批人。CSS3、Sass和Drupal中国布道者。2014年出版《 图解CSS3:核心技术与案例实战 》。