对于初学React的同学而言,这并不是一件易事。就拿我自己来说,都不知道从何下手,应该如何去学习才能开始使用React。就算你对React不陌生,学习React也常会碰到一些瓶颈。比如说新颖的概念、开发工具的使用、抽象的名词、快速变化的生态环境等等。也就是说,一旦开始学习React,你会发觉要学的东西越来越多,甚至可能还没开始碰到React就被这些东西给吓跑了(特别是对于初学者,听到这些东东就傻眼了)。
这篇文章不是来介绍怎么学习React,而是要让初学者或没学过React的同学对React的一些重要概念有所了解。
元素(Element)
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
)。
数据呈现:JSX
前面提到过了,React的ELement虽然看上去像HTML,其实它的专业术语称之为JSX。那么在学习React之前,很有必要要了解一下JSX。
了解JSX
JSX可以看做是JavaScript的拓展,有点类似于XML。使用React可以进行JSX语法到JavaScript的转换。它不是新语言,也没不用改变JavaScript的语法,只是对JavaScript的拓展。当然,使用React开发并不一定非要使用JSX(感兴趣的可以看看这篇译文)。
前面说过,JSX语法跟XML或者HTML很相似,但有一些细节需要注意:
- 属性表达式: 要使用JavaScript表达式作为属性值,只需把这个表达式用一对大括号
{}
包裹起来,不要用引号""
- 嵌套其他组件:只需要引入相应组件,并将其作为一个标签插入JSX即可
- Style属性:我们可以在JSX内部写CSS,不过要遵循一下格式
- HTML转义: React默认会进行HTML转义,用以避免XSS攻击
- 注释:只需要在一个标签的子节点内(非最外层)小心地用
{}
包围要注释的部分即可 - JSX最外层标签必须是唯一的
- React的JSX里约定分别使用首字母大、小写来区分本地组件的类和HTML标签
- 由于JSX就是JavaScript,一些JavaScript的标识符,比如
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中有两种类型的数据模型: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组件
知道React中Element是什么,那了解React中的Component(组件)也就很好理解了。在React中是通过Element去描述Component,其本质上也可以说Component是Element。
React组件的特色
React中的Component有两大特色:
- 封装Element tree(利于前端模块化)
- 可以使用JavaScript Function、Class来写
下在是使用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的组件生命周期,就如同人一样,也有生老病死,其大致可以分为七个周期:
- Mounting: 取得组件的初始参数和状态,并且进行第一次的渲染
- DOM Muntings Complete: 渲染完成后,DOM也会跟着更新,使用者可以看到最新的画面
- Mounted: 组件已经更新完成,等待其他变化
- Receiving state: 组件的状态
state
已经被更新,并且重新渲染了,所以回到第2
个周期 - Unmounting: 组件即将被移除
- Unmounted: 组件的死亡阶段
除了知道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>);
}
}
下面简单介绍了这些方法的使用,如果你想了解更详细的信息,可以阅读官方文件。
getInitialState()
- 组件首次挂载之前调用一次(仅执行一次)
- 用户初始化
this.state
- 其返回值将作为
this.state
的初始值
getDefaultProps()
- 当组件类被创建时调用一次并缓存其返回值
- 用于设置
this.props
的初始值 - 如果其父组件没指定
props
中的某个键,那么getDefaultProps()
的返回对象中的相应属性将会合并到this.props
中作为初始值。也就是说,当父集没有传入props
时,getDefaultProps()
方法可以保证this.props.xxx
有默认值,并且它的返回值将被缓存,我们可以直接使用props
而不必重复编写一些无意义的代码 - 由于该方法在任何实例创建之前调用,因此它不能依赖于
this.props
,其返回值将在全部实例中共享
propTypes
- 随着应用不断壮大,确保组件被正确使用变得非常有必要。
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数组
- 组件是React里复用代码的最佳方式,
mixins
数组可以让我们在多个组件间复用一些方法。大致等同于将mixins
数组中的组件方法引入到this
下,以便我们可以直接调用 - 如果一个组件使用了多个
mixin
,并且有多个mixin
定义了同样的生命周期方法,所有这些生命周期方法都保证会被执行到。其执行顺序是:先按照mixin
引入顺序执行mixin
里面的方法,最后执行组件内定义的方法
statics
statics
对象允许我们定义一些能够被组件类调用的静态方法- 在这里定义的方法都是静态的,所以我们可以在任何组件实例创建之前调用它们
- 这些方法不能获取组件的
props
和state
,但是却可以接受参数
componentWillMount
- 在初始化渲染执行前立即调用且仅调用一次
- 服务端和客户端都只会调用一次。如果在这个方法内部调用
setState
来改变this.state
的值,更新后的this.state
值将被render
方法感知到,并且render
只会执行一次,尽管state
改变了
componentDidMount
- 在初始化
render
渲染方法之后,立即调用一次,仅客户端有效(服务端不会调用) - 在生命周期中的这个时间点,组件拥有一个DOM展现,可以通过
this.getDOMNode()
来获取相应的DOM节点。我们可以在这个方法中与其他JavaScript框架进行集成、处理一些渲染后的逻辑(比如说绑定一些事件等)、发送Ajax请求或是设置定时器方法(setTimeout/setInterval
)等
componentWillReceiveProps
- 组件每次收到新的
props
时调用,初始化渲染时不会被调用 - 该方法可以作为React在
prop
传入之后,render()
方法执行之前更新state
的合适时机。老的props
可以通过this.props
获取到 - 在初始化渲染时,该方法不会被调用。在该方法中调用
this.setState()
shouldComponentUpdate
- 在收到新的
props
或者state
,将要渲染之前调用 - 当我们确定新的
props
或state
不需要导致组件更新时,应在此方法中return false;
,也就是说当shouldComponentUpdate()
返回false
时,render()
方法不会被执行(componentWillUpdate()
、componentDidUpdate()
都将不会被调动),直到下一次state
改变。默认情况下,shouldComponentUpdate()
总是返回true
- 该方法在初始化渲染时不会被调用,且在使用
forceUpdate()
时也不会被调用
componentDidUpdate
- 在组件更新已经同步到DOM中之后立刻被调用。也就是在除了初始化
render
之后 - 使用该方法可以在组件更新之后操作DOM元素。基本等同于
componentDidMount()
,唯一不同在于首次初始化render
渲染完成后将执行componentDidMount()
方法,而之后每次渲染完成都会执行componentDidUpdate()
方法。我们可以使用this.getDOMNode()
来访问DOM节点 - 该方法不会在初始化渲染的时候调用
componentWillUnmount
- 在组件从DOM中移除的时候立刻被调用
- 我们可以在该方法中执行任何必要的清理,比如无效的定时器,或者清除在
componentDidMount()
中创建的DOM元素等
对于React组件的生命周期来说,我们需要了解的是:
- React的组件是有生命周期的
- 生命周期的每一个阶段,都会调用相对应的方法
- 操作这些方法,可以拦截React组件的变化,并且做相应的处理
总结
当我们开发Web网站或应用时,前端架构变得越来越复杂,有大量的 DOM需要处理的状况之下,性能就开始会出现问题。使用React则可以帮助我们省下处理DOM的麻烦,因为使用React的话,从头到尾我们都没有必要去操作真正的DOM,我们要做的事情仅仅是处理物件(描述Element、Component的物件)。
因为React生态系统过于庞大,需要学习的东西越来越多,但要记住一件很重要的事情,学习React其实也就是学习JavaScript。同时使用React开发会使用到最基本的JavaScript及Web API(XHR或Fetch),当然如果你想快速完成一个案例或应用,那么React是一个很好的选择。
另外,React还有很多有趣的东西值得讨论和学习,而这篇文章只是涉及了React最基础的部分,也向大家阐述了学习React最重要的一件事情:React是处理物件而不是处理DOM。
如需转载,烦请注明出处:http://www.w3cplus.com/react/react-beginner-intro.html