React的一些概念
对于初学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。
如需转载,烦请注明出处:https://www.fedev.cn/react/react-beginner-intro.htmlZoom Lebron XII 12
 