元素react
元素是构建React应用的最小单位。元素所描述的也就是你在浏览器中可以看到的东西。根据咱们在上节课中讲到的内容,咱们在编写React代码时通常用JSX来描述React元素。ajax
在做用上,咱们能够把React元素理解为DOM元素;但实际上,React元素只是JS当中普通的对象。React内部实现了一套叫作React DOM的东西,或者咱们称之为Virtual DOM也就是虚拟DOM.经过一个树状结构的JS对象来模拟DOM树。算法
说到这里咱们能够稍微讲一下,React为何会有这一层虚拟DOM呢?在课程介绍中咱们曾经提到过,React很快、很轻。它之因此快就是由于这一套虚拟DOM的存在,React内部还实现了一个低复杂度高效率的Diff算法,不一样于以往框架,例如angular使用的脏检查。在应用的数据改变以后,React会尽力少地比较,而后根据虚拟DOM只改变真实DOM中须要被改变的部分。React也藉此实现了它的高效率,高性能。json
固然这不是虚拟DOM惟一的意义,经过这一层单独抽象的逻辑让React有了无限的可能,就好比react native的实现,可让你只掌握JS的知识也能在其余平台系统上开发应用,而不仅是写网页,甚至是以后会出现的React VR或者React物联网等等别的实现。redux
话说回来,元素也就是React DOM之中描述UI界面的最小单位。刚才咱们说到了,元素其实就是普通的JS对象。不过咱们用JSX来描述React元素在理解上可能有些困难,事实上,咱们也能够不使用JSX来描述:浏览器
const element = <h1>Hello, world</h1>; // 用JSX描述就至关因而调用React的方法建立了一个对象 const element = React.createElement('h1', null, 'Hello, world');
组件服务器
要注意到,在React当中元素和组件是两个不一样的概念,之因此在前面讲了这么多,就是担忧你们不当心会混淆这两个概念。首先咱们须要明确的是,组件是构建在元素的基础之上的。微信
React官方对组件的定义呢,是指在UI界面中,能够被独立划分的、可复用的、独立的模块。其实就相似于JS当中对function函数的定义,它通常会接收一个名为props的输入,而后返回相应的React元素,再交给ReactDOM,最后渲染到屏幕上。框架
新版本的React里提供了两种定义组件的方法。固然以前的React.createClass也能够继续用,不过咱们在这里先不归入咱们讨论的范围。less
第一种函数定义组件,很是简单啦,咱们只须要定义一个接收props传值,返回React元素的方法便可:
function Title(props) { return <h1>Hello, {props.name}</h1>; }
甚至使用ES6的箭头函数简写以后能够变成这样:
const Title = props => <h1>Hello, {props.name}</h1>
第二种是类定义组件,也就是使用ES6中新引入的类的概念来定义React组件:
class Title extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } }
以后呢,根据咱们在上一节课中了解到的,组件在定义好以后,能够经过JSX描述的方式被引用,组件之间也能够相互嵌套和组合。
接下来咱们还会介绍一些更深刻的关于组件概念,如今听起来可能会比较抽象枯燥,不过接下来要介绍的这几个概念在以后的课程中都是会被应用到的,同窗们也能够根据本身的实际状况,在学习完后续的课程以后,再返回来听听看,相信必定会对你理解React有所帮助。
首先是最重要的一组概念:展现组件与容器组件。一样,在课程介绍中咱们提到的,React并非传统的MVVM框架,它只是在V层,视图层上下功夫。同窗们应该对MVVM或MVC都有所了解,那么既然咱们的框架如今只有V层的话,在实际开发中应该如何处理数据与视图的关系呢?
为了解决React只有V层的这个问题,更好地区分咱们的代码逻辑,展现组件与容器组件这一对概念就被引入了。这一样也是咱们在开发React应用时的最佳实践。
咱们仍是先看一个具体的例子来解释这两个概念:
class CommentList extends React.Component { constructor(props) { super(props) this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}) }.bind(this) }) } renderComment({body, author}) { return <li>{body}—{author}</li> } render() { return <ul> {this.state.comments.map(this.renderComment)} </ul> } }
这是一个回复列表组件,乍看上去很正常也很合理。但实际上在开发React应用时,咱们应该避免写出这样的组件,由于这类组件担负的功能太多了。它只是一个单一的组件,但须要同时负责初始化state,经过ajax获取服务器数据,渲染列表内容,在实际应用中,可能还会有更多的功能依赖。这样,在后续维护的时候,不论是咱们要修改服务器数据交互仍是列表样式内容,都须要去修改同一个组件,逻辑严重耦合,多个功能在同一个组件中维护也不利于团队协做。
经过应用展现组件与容器组件的概念,咱们能够把上述的单一组件重构为一个展现回复列表组件和回复列表容器:
// 展现组件 class CommentList extends React.Component { constructor(props) { super(props); } renderComment({body, author}) { return <li>{body}—{author}</li>; } render() { return <ul> {this.props.comments.map(this.renderComment)} </ul>; } } // 容器组件 class CommentListContainer extends React.Component { constructor() { super() this.state = { comments: [] } } componentDidMount() { $.ajax({ url: "/my-comments.json", dataType: 'json', success: function(comments) { this.setState({comments: comments}) }.bind(this) }) } render() { return <CommentList comments={this.state.comments} /> } }
像这样回复列表如何展现与如何获取回复数据的逻辑就被分离到两个组件当中了。咱们再来明确一下展现组件和容器组件的概念:
展现组件
容器组件
那么这样写具体有什么好处呢?
有状态组件
意思是这个组件可以获取储存改变应用或组件自己的状态数据,在React当中也就是state,一些比较明显的特征是咱们能够在这样的组件当中看到对this.state的初始化,或this.setState方法的调用等等。
无状态组件
这样的组件通常只接收来自其余组件的数据。通常这样的组件中只能看到对this.props的调用,一般能够用函数定义组件的方式声明。它自己不会掌握应用的状态数据,即便触发事件,也是经过事件处理函数传递到其余有状态组件当中再对state进行操做。
咱们仍是来看具体的例子比较能清楚地说明问题,与此同时,咱们已经介绍了三组概念,为了防止混淆,我这里特地使用了两个展现组件来作示例,其中一个是有状态组件,另外一个是无状态组件,也是为了证实,并非全部的展现组件都是无状态组件,全部的容器组件都是有状态组件。再次强调一下,这是两组不一样的概念,以及对组件不一样角度的划分方式。
//Stateful Component class StatefulLink extends React.Component { constructor(props) { super(props) this.state = { active: false } } handleClick() { this.setState({ active: !this.state.active }) } render() { return <a style={{ color: this.state.active ? 'red' : 'black' }} onClick={this.handleClick.bind(this)} > Stateful Link </a> } } // Stateless Component class StatelessLink extends React.Component { constructor(props) { super(props) } handleClick() { this.props.handleClick(this.props.router) } render() { const active = this.props.activeRouter === this.props.router return ( <li> <a style={{ color: active ? 'red' : 'black' }} onClick={this.handleClick.bind(this)} > Stateless Link </a> </li> ) } } class Nav extends React.Component { constructor() { super() this.state={activeRouter: 'home'} } handleSwitch(router) { this.setState({activeRouter: router}) } render() { return ( <ul> <StatelessLink activeRouter={this.state.activeRouter} router='home' handleClick={this.handleSwitch.bind(this)} /> <StatelessLink activeRouter={this.state.activeRouter} router='blog' handleClick={this.handleSwitch.bind(this)} /> <StatelessLink activeRouter={this.state.activeRouter} router='about' handleClick={this.handleSwitch.bind(this)} /> </ul> ) } }
上述的例子可能稍有些复杂,事实上,在React的实际开发当中,咱们编写的组件大部分都是无状态组件。毕竟React的主要做用是编写用户界面。再加上ES6的新特性,绝大多数的无状态组件均可以经过箭头函数简写成相似下面这样:
/* function SimpleButton(props) { return <button>{props.text}</button> } */ const SimpleButton = props => <button>{props.text}</button>
受控组件
通常涉及到表单元素时咱们才会使用这种分类方法,在后面一节课程表单及事件处理中咱们还会再次谈论到这个话题。受控组件的值由props或state传入,用户在元素上交互或输入内容会引发应用state的改变。在state改变以后从新渲染组件,咱们才能在页面中看到元素中值的变化,假如组件没有绑定事件处理函数改变state,用户的输入是不会起到任何效果的,这也就是“受控”的含义所在。
非受控组件
相似于传统的DOM表单控件,用户输入不会直接引发应用state的变化,咱们也不会直接为非受控组件传入值。想要获取非受控组件,咱们须要使用一个特殊的ref属性,一样也可使用defaultValue属性来为其指定一次性的默认值。
咱们仍是来看具体的例子:
class ControlledInput extends React.Component { constructor() { super() this.state = {value: 'Please type here...'} } handleChange(event) { console.log('Controlled change:',event.target.value) this.setState({value: event.target.value}) } render() { return ( <label> Controlled Component: <input type="text" value={this.state.value} onChange={(e) => this.handleChange(e)} /> </label> ); } } class UncontrolledInput extends React.Component { constructor() { super(); } handleChange() { console.log('Uncontrolled change:',this.input.value); } render() { return ( <label> Uncontrolled Component: <input type="text" defaultValue='Please type here...' ref={(input) => this.input = input} onChange={() =>this.handleChange()} /> </label> ); } }
一般状况下,React当中全部的表单控件都须要是受控组件。但正如咱们对受控组件的定义,想让受控组件正常工做,每个受控组件咱们都须要为其编写事件处理函数,有的时候确实会很烦人,比方说一个注册表单你须要写出全部验证姓名电话邮箱验证码的逻辑,固然也有一些小技巧可让同一个事件处理函数应用在多个表单组件上,但生产开发中并无多大实际意义。更有可能咱们是在对已有的项目进行重构,除了React以外还有一些别的库须要和表单交互,这时候使用非受控组件可能会更方便一些。
前面咱们已经提到了,React当中的组件是经过嵌套或组合的方式实现组件代码复用的。经过props传值和组合使用组件几乎能够知足全部场景下的需求。这样也更符合组件化的理念,就好像使用互相嵌套的DOM元素同样使用React的组件,并不须要引入继承的概念。
固然也不是说咱们的代码不能这么写,来看下面这个例子:
// Inheritance class InheritedButton extends React.Component { constructor() { super() this.state = { color: 'red' } } render() { return ( <button style={{backgroundColor: this.state.color}} class='react-button'>Inherited Button</button> ) } } class BlueButton extends InheritedButton { constructor() { super() this.state = { color: '#0078e7' } } } // Composition const CompositedButton = props => <button style={{backgroundColor:props.color}}>Composited Button</button> const YellowButton = () => <CompositedButton color='#ffeb3b' />
但继承的写法并不符合React的理念。在React当中props实际上是很是强大的,props几乎能够传入任何东西,变量、函数、甚至是组件自己:
function SplitPane(props) { return ( <div className="SplitPane"> <div className="SplitPane-left"> {props.left} </div> <div className="SplitPane-right"> {props.right} </div> </div> ); } function App() { return ( <SplitPane left={ <Contacts /> } right={ <Chat /> } /> ); }
React官方也但愿咱们经过组合的方式来使用组件,若是你想实现一些非界面类型函数的复用,能够单独写在其余的模块当中在引入组件进行使用。
本文为《从零学习React技术栈教程·理论篇》当中的一节内容,学习React,阅读更多用心有爱的教程内容,请关注 余博伦 微信公众号。