码迷,mamicode.com
首页 > 其他好文 > 详细

React

时间:2015-08-20 01:33:23      阅读:689      评论:0      收藏:0      [点我收藏+]

标签:react   web   dom   frameworks   

Introduction

技术分享

React并不是一个完整的MVC或者MVVM框架,它与Angular也是负责不同的方面,它最大的功能是提供一个高效的视图层。React提供了一些新颖的概念、库和编程原则让你能够同时在服务端和客户端编写快速、紧凑、漂亮的代码来构建你的web应用。如果你使用React,那么可能会涉及到一些常用的概念或技术,包括:
  • ES6 React
  • 虚拟DOM(virtual DOM)
  • 组件驱动开发(component-driven development)
  • 不变性(immutability)
  • 自上而下的渲染(top-down rendering)
  • 渲染路径和优化
  • 打包工具, ES6, 构建请求, debugging, 路由等
  • 同构React(isomorphic React)

    在具体的React实践中,考虑到纯粹的UI或者UX设计人员,他们可能只会将CSS与HTML进行组合,换言之,大量的赋值还是会放置在HTML而非JSX中,建议还是可以运用jQuery+React或者Angular+React的方式。

Virtual Dom

如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。

而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升。

技术分享

React渲染出来的HTML标记都包含了`data-reactid`属性,这有助于React中追踪DOM节点。

单向数据流

在React中,应用利用State与Props对象实现单向数据流的传递。换言之,在一个多组件的架构中,某个父类组件只会负责响应自身的State,并且通过Props在链中传递给自己的子元素。

/** @jsx React.DOM */

var FilteredList = React.createClass({
  filterList: function(event){
    var updatedList = this.state.initialItems;
    updatedList = updatedList.filter(function(item){
      return item.toLowerCase().search(
        event.target.value.toLowerCase()) !== -1;
    });
    this.setState({items: updatedList});
  },
  getInitialState: function(){
     return {
       initialItems: [
         "Apples",
         "Broccoli",
         "Chicken",
         "Duck",
         "Eggs",
         "Fish",
         "Granola",
         "Hash Browns"
       ],
       items: []
     }
  },
  componentWillMount: function(){
    this.setState({items: this.state.initialItems})
  },
  render: function(){
    return (
      <div className="filter-list">
        <input type="text" placeholder="Search" onChange={this.filterList}/>
      <List items={this.state.items}/>
      </div>
    );
  }
});

var List = React.createClass({
  render: function(){
    return (
      <ul>
      {
        this.props.items.map(function(item) {
          return <li key={item}>{item}</li>
        })
       }
      </ul>
    )  
  }
});

React.render(<FilteredList/>, document.getElementById(‘mount-point‘));

React

Quick Start

Usage

基本的React的页面形式如下所示:

    <!DOCTYPE html>
    <html>
      <head>
        <script src="../build/react.js"></script>
        <script src="../build/JSXTransformer.js"></script>
      </head>
      <body>
        <div id="example"></div>
        <script type="text/jsx">
          // ** Our code goes here! **
        </script>
      </body>
    </html>

React独创了一种JS、CSS和HTML混写的JSX格式,可以通过在页面中引入JSXTransformer这个文件进行客户端的编译,不过还是推荐在网页端编译,笔者习惯使用Babel这个平台。

HelloWorld

var HelloMessage = React.createClass({
  render: function() {
    return <div>Hello {this.props.name}</div>;
  }
});

React.render(
  <HelloMessage name="John" />,
  document.getElementById(‘container‘)
);

React.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。要注意的是,React的渲染函数并不是简单地把HTML元素复制到页面上,而是维护了一张Virtual Dom映射表。

class ExampleComponent extends React.Component {
 constructor() {
  super();
  this. _handleClick = this. _handleClick.bind(this);
  this.state = Store.getState();
 }
 // ...
}

Setup:开发环境搭建

完整的React开发环境应该包括了JSX/ES6的解析以及模块化管理,笔者在这里是选用了WebPack与Babel。Webpack是一个强大的包管理以及编译工具,

参考资料

Webpack

Webpack是一个非常强大依赖管理与打包工具,其基本的配置方式可以如下:
var path = require(‘path‘);
var node_modules = path.resolve(__dirname, ‘node_modules‘);
var pathToReact = path.resolve(node_modules, ‘react/dist/react.min.js‘);

config = {
    entry: [‘webpack/hot/dev-server‘, path.resolve(__dirname, ‘app/main.js‘)],
    resolve: {
        alias: {
          ‘react‘: pathToReact
        }
    },
    output: {
        path: path.resolve(__dirname, ‘build‘),
        filename: ‘bundle.js‘,
    },
    module: {
        loaders: [{
            test: /\.jsx?$/,
            loader: ‘babel‘
        }],
        noParse: [pathToReact]
    }    
};

module.exports = config;

Project Structure:项目结构

一个典型的项目结构你可以参考这个仓库

config/  
    app.js
    webpack.js (js config over json -> flexible)
src/  
  app/ (the React app: runs on server and client too)
    components/
      __tests__ (Jest test folder)
      AppRoot.jsx
      Cart.jsx
      Item.jsx
    index.js (just to export app)
    app.js
  client/  (only browser: attach app to DOM)
    styles/
    scripts/
      client.js
    index.html
  server/
    index.js
    server.js
.gitignore
.jshintrc
package.json  
README.md

Integrated With Angular

Angular与React是笔者喜欢的两个框架,二者可以相辅相成。可以查看笔者的这个库。

JSX

HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写。

var names = [‘Alice‘, ‘Emily‘, ‘Kate‘];

React.render(
  <div>
  {
    names.map(function (name) {
      return <div>Hello, {name}!</div>
    })
  }
  </div>,
  document.getElementById(‘example‘)
);

上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员:

var arr = [
  <h1>Hello world!</h1>,
  <h2>React is awesome</h2>,
];
React.render(
  <div>{arr}</div>,
  document.getElementById(‘example‘)
);

Transfer

JSX编译器的核心是将基于XML的语言编译成JS代码,主要是依赖于React.createElment函数。

var Nav;
// Input (JSX):
var app = <Nav color="blue" />;
// Output (JS):
var app = React.createElement(Nav, {color:"blue"});
var Nav, Profile;
// Input (JSX):
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// Output (JS):
var app = React.createElement(
  Nav,
  {color:"blue"},
  React.createElement(Profile, null, "click")
);

JavaScript Expressions

属性表达式

如果需要在HTML中混入JavaScript变量值,需要利用{}来代替”“。

// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ‘‘} />;
// Output (JS):
var person = React.createElement(
  Person,
  {name: window.isLoggedIn ? window.name : ‘‘}
);

Boolean Attributes

// These two are equivalent in JSX for disabling a button
<input type="button" disabled />;
<input type="button" disabled={true} />;

// And these two are equivalent in JSX for not disabling a button
<input type="button" />;
<input type="button" disabled={false} />;

Child Expressions

// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// Output (JS):
var content = React.createElement(
  Container,
  null,
  window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);

Comments

var content = (
  <Nav>
    {/* child comment, put {} around */}
    <Person
      /* multi
         line
         comment */
      name={window.isLoggedIn ? window.name : ‘‘} // end of line comment
    />
  </Nav>
);

Components

React提供了和以往不一样的方式来看待视图,它以组件开发为基础。组件是React的核心概念,React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类。对React应用而言,你需要分割你的页面,使其成为一个个的组件。也就是说,你的应用是由这些组件组合而成的。你可以通过分割组件的方式去开发复杂的页面或某个功能区块,并且组件是可以被复用的。这个过程大概类似于用乐高积木去瓶装不同的物体。我们称这种编程方式称为**组件驱动开发**。

LifeCycle

组件的生命周期分成三个状态:

  • Mounting:已插入真实 DOM,即Initial Render

  • Updating:正在被重新渲染,即Props与State改变

  • Unmounting:已移出真实 DOM,即Component Unmount

    React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

  • componentWillMount()

  • componentDidMount()

  • componentWillUpdate(object nextProps, object nextState)

  • componentDidUpdate(object prevProps, object prevState)

  • componentWillUnmount()

此外,React 还提供两种特殊状态的处理函数。

  • componentWillReceiveProps(object nextProps):已加载组件收到新的参数时调用
  • shouldComponentUpdate(object nextProps, object nextState):组件判断是否重新渲染时调用

Ini-tial Render

技术分享

Props Change

技术分享

State Change

技术分享

这里可以看出,Props比State的改变会有多出一个shouldComponentUpdate的回调方法。

Com-po-nent Unmount

技术分享

总结而言,一个完整的React Component的写法应该如下:
/**
 * @jsx React.DOM
 */

var React = require(‘react‘),
    MyReactComponent = React.createClass({

    // The object returned by this method sets the initial value of this.state
    getInitialState: function(){
        return {};
    },

    // The object returned by this method sets the initial value of this.props
    // If a complex object is returned, it is shared among all component instances      
    getDefaultProps: function(){
        return {};
    },

    // Returns the jsx markup for a component
    // Inspects this.state and this.props create the markup
    // Should never update this.state or this.props
    render: function(){
        return (<div></div>);
    },

    // An array of objects each of which can augment the lifecycle methods
    mixins: [],

    // Functions that can be invoked on the component without creating instances
    statics: {
        aStaticFunction: function(){}
    },

    // -- Lifecycle Methods --

    // Invoked once before first render
    componentWillMount: function(){
        // Calling setState here does not cause a re-render
    },

    // Invoked once after the first render
    componentDidMount: function(){
        // You now have access to this.getDOMNode()
    },

    // Invoked whenever there is a prop change
    // Called BEFORE render
    componentWillReceiveProps: function(nextProps){
        // Not called for the initial render
        // Previous props can be accessed by this.props
        // Calling setState here does not trigger an an additional re-render
    },

    // Determines if the render method should run in the subsequent step
    // Called BEFORE a render
    // Not called for the initial render
    shouldComponentUpdate: function(nextProps, nextState){
        // If you want the render method to execute in the next step
        // return true, else return false
        return true;
    },

    // Called IMMEDIATELY BEFORE a render
    componentWillUpdate: function(nextProps, nextState){
        // You cannot use this.setState() in this method
    },

    // Called IMMEDIATELY AFTER a render
    componentDidUpdate: function(prevProps, prevState){
    },

    // Called IMMEDIATELY before a component is unmounted
    componentWillUnmount: function(){
    }

}); 

module.exports = MyReactComponent;

Props

getDefaultProps

设置默认的Props.

Attributes

children

this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点。

    var NotesList = React.createClass({
      render: function() {
        return (
          <ol>
          {
            this.props.children.map(function (child) {
              return <li>{child}</li>
            })
          }
          </ol>
        );
      }
    });

    React.render(
      <NotesList>
        <span>hello</span>
        <span>world</span>
      </NotesList>,
      document.body
    );

其效果图如下所示:

技术分享

Validation

React.createClass({
  propTypes: {
    // You can declare that a prop is a specific JS primitive. By default, these
    // are all optional.
    optionalArray: React.PropTypes.array,
    optionalBool: React.PropTypes.bool,
    optionalFunc: React.PropTypes.func,
    optionalNumber: React.PropTypes.number,
    optionalObject: React.PropTypes.object,
    optionalString: React.PropTypes.string,

    // Anything that can be rendered: numbers, strings, elements or an array
    // containing these types.
    optionalNode: React.PropTypes.node,

    // A React element.
    optionalElement: React.PropTypes.element,

    // You can also declare that a prop is an instance of a class. This uses
    // JS‘s instanceof operator.
    optionalMessage: React.PropTypes.instanceOf(Message),

    // You can ensure that your prop is limited to specific values by treating
    // it as an enum.
    optionalEnum: React.PropTypes.oneOf([‘News‘, ‘Photos‘]),

    // An object that could be one of many types
    optionalUnion: React.PropTypes.oneOfType([
      React.PropTypes.string,
      React.PropTypes.number,
      React.PropTypes.instanceOf(Message)
    ]),

    // An array of a certain type
    optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),

    // An object with property values of a certain type
    optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),

    // An object taking on a particular shape
    optionalObjectWithShape: React.PropTypes.shape({
      color: React.PropTypes.string,
      fontSize: React.PropTypes.number
    }),

    // You can chain any of the above with `isRequired` to make sure a warning
    // is shown if the prop isn‘t provided.
    requiredFunc: React.PropTypes.func.isRequired,

    // A value of any data type
    requiredAny: React.PropTypes.any.isRequired,

    // You can also specify a custom validator. It should return an Error
    // object if the validation fails. Don‘t `console.warn` or throw, as this
    // won‘t work inside `oneOfType`.
    customProp: function(props, propName, componentName) {
      if (!/matchme/.test(props[propName])) {
        return new Error(‘Validation failed!‘);
      }
    }
  },
  /* ... */
});

State

React不提倡数据的双向绑定,而在用户行为下面产生的数据更新,React建议还是通过事件机制来处理。譬如下述例子中,输入框文本内容的改变,还是通过onChange事件,然后出发状态机的变化。


    var LikeButton = React.createClass({
      getInitialState: function() {
        return {liked: false};
      },
      handleClick: function(event) {
        this.setState({liked: !this.state.liked});
      },
      render: function() {
        var text = this.state.liked ? ‘like‘ : ‘haven\‘t liked‘;
        return (
          <p onClick={this.handleClick}>
            You {text} this. Click to toggle.
          </p>
        );
      }
    });

    React.render(
      <LikeButton />,
      document.getElementById(‘example‘)
    );

Props与State对比

参考资料

Props VS State

组件的主要职责是将原始数据转化为HTML中的富文本格式,而Props与State协作完成这件事,换言之,Props与State的并集即是全部的原始数据。Props与State之间也是有很多交集的,譬如:

  • Props与State都是JS对象。
  • Props与State的值的改变都会触发界面的重新渲染。
  • Props与State都是确定性的,即在确定的Props或者State的值的情况下都会得出相同的界面。

不过Props顾名思义,更多的是作为Component的配置项存在。Props往往是由父元素指定并且传递给自己的子元素,不过自身往往不会去改变Props的值。另一方面,State在组件被挂载时才会被赋予一个默认值,而常常在与用户的交互中发生更改。往往一个组件独立地维护它的整个状态机,可以认为State是一个私有属性。他们的对比如下:

描述 Props State
是否可以从父元素获取初始值 Yes Yes
是否可以被父元素改变 Yes No
是否可以设置默认值 Yes Yes
是否可以在组件内改变 No Yes
是否可以设置为子元素的初始值 Yes Yes
是否可以在子元素中改变 Yes No

Interactivity and Dynamic UIs

React.findDOMNode()

组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 React.findDOMNode 方法。

var MyComponent = React.createClass({
  handleClick: function() {
    React.findDOMNode(this.refs.myTextInput).focus();
  },
  render: function() {
    return (
      <div>
        <input type="text" ref="myTextInput" />
        <input type="button" value="Focus the text input" onClick={this.handleClick} />
      </div>
    );
  }
});

React.render(
  <MyComponent />,
  document.getElementById(‘example‘)
);

需要注意的是,由于 React.findDOMNode 方法获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个方法,否则会返回 null 。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会调用 React.findDOMNode 方法。

getInitialState

设置State的初始状态。

var MyComponent = React.createClass({
    getInitialState: function(){
        return {
            count: 5
        }
    },
    render: function(){
        return (
            <h1>{this.state.count}</h1>
        )
    }
});

Style

参考资料

Inline-style

在React中,如果要使用行内元素,不可以直接使用style="”这种方式,可以有:
import React from ‘react‘;

var style = {
  backgroundColor: ‘#EEE‘
};

export default React.createClass({
  render: function () {
    return (
      <div style={style}>
      //或者<div style={{backgroundColor: ‘#EEE‘}}>
        <h1>Hello world</h1>
      </div>
    )
  }
});

Class

你可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。

app/components/MyComponent.css

.MyComponent-wrapper {
  background-color: #EEE;
}

app/components/MyComponent.jsx

import ‘./MyComponent.css‘;
import React from ‘react‘;

export default React.createClass({
  render: function () {
    return (
      <div className="MyComponent-wrapper">
        <h1>Hello world</h1>
      </div>
    )
  }
});

Event

React对于事件的支持非常完善,可以查看这里

TouchEvent

If you’d like to use React on a touch device such as a phone or tablet, simply call React.initializeTouchEvents(true); to enable touch event handling.

接口暴露

譬如在某个子组件中,提供了某个方法:

var ButtonComponent = React.createClass({
    getDragonKillingSword: function(){
        //送宝刀
    },
    render: function(){
        return (<button onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
    }
});

如果在父组件中想手动调用该方法,则可以利用ref方式:

var ImDaddyComponent = React.createClass({
  render: function(){
    return (
      <div>
        //其他组件
        <ButtonComponent />
        //其他组件
      </div>
    );
  }
});

在父组件的功能方程中:

this.refs.getSwordButton.getDragonKillingSword();

反之,如果需要在子组件中调用父组件的方法,则可以直接将父组件的方法作为Props参数传入到子组件中:

<ButtonComponent clickCallback={this.getSwordButtonClickCallback}/>

Ajax

组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI。

    var UserGist = React.createClass({
      getInitialState: function() {
        return {
          username: ‘‘,
          lastGistUrl: ‘‘
        };
      },

      componentDidMount: function() {
        $.get(this.props.source, function(result) {
          var lastGist = result[0];
          if (this.isMounted()) {
            this.setState({
              username: lastGist.owner.login,
              lastGistUrl: lastGist.html_url
            });
          }
        }.bind(this));
      },

      render: function() {
        return (
          <div>
            {this.state.username}‘s last gist is
            <a href={this.state.lastGistUrl}>here</a>.
          </div>
        );
      }
    });

    React.render(
      <UserGist source="https://api.github.com/users/octocat/gists" />,
      document.body
    );

不过笔者习惯还是将整个获取数据,处理数据的业务逻辑放在Angular中进行。

Test:组件测试

对于React组件的测试,这里推荐使用[Jest](https://facebook.github.io/jest/),Jest也是由Facebook提供的测试框架,并且有很多强大的特性,但这里并会详细的介绍它们。关于Jest,我推荐你阅读和尝试来自Facebook的[Tutorial](https://facebook.github.io/jest/docs/tutorial-react.html#content)。对于ES6代码的测试,你可以参考 [React ES6 Testing](https://github.com/facebook/jest/tree/master/examples/react-es6)。

Reference

Tutorial

Practice

版权声明:本文为博主原创文章,未经博主允许不得转载。

React

标签:react   web   dom   frameworks   

原文地址:http://blog.csdn.net/wxyyxc1992/article/details/47791709

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!