React 是 Facebook 里一群牛 X 的码农折腾出的牛X的框架。 实现了一个虚拟 DOM 用 DOM 的方式将需要的组件秒加 用不着的秒删。React 扮演着 MVC 结构中 V 的角色 不过你要是 Flux 搭配使用 你就有一个很牛X的能让轻松让 M 和 V 同步的框架了 Flux 的事以后再说~

组件们

在 React 中 你可以创建一个有特殊功能的组件 这在 HTML 元素里你是打着灯笼也找不到的 比如这个教程里的下拉导航。每个组件都有自己的地盘(scope) 所以定义一个组件后可以反复的用反复的用 根本就不需要和别的组件交互!

每个组件都有一个功能叫 render 它可以高效的返回要射到浏览器上 HTML。也可以调用 React 的其他组件 这意味这一入 React 深似海 哦 不对 是只有想不到 没有做不到。

JSX

如果你经常注意React你也许会发现有个东西叫JSX。JSX允许在Javascript中写HTML 而不是用HTML包含Javascript。它能帮助快速开发 因为不用担心字符串和换行等。你可以在浏览器中运行JSX 但是不推荐 因为这样会减慢你的页面速度。gulp和grunt为你的预处理任务提供了一个JSX解释器 所以如果你想使用JSX 我建议开启这个功能。

使用 JSX

如前面所说 JSX每一个组件都有一个 render 方法 这个方法产生一个”ViewModel(视图模型)” – 在返回HTML到组件之前 你可以将这个模型(ViewModel)的信息放入视图中 意味着你的HTML会根据这个模型动态改变(例如一个动态列表)。

一旦你完成了操作 就可以返回你想渲染(render)的东西 使用JSX来做是如此简单

var ExampleComponent = React.createClass({  render: function () {    return (      <div className="navigation">        Hello World!      </div>      );  }});

如果你要在你的浏览器中运行这段代码 只会得到语法错误的提示 因为在Javascript中<和>字符要用引号包含起来。当你在代码上运行JSX解释器 它将被转换成这样

var ExampleComponent = React.createClass({    render: function () {        return (            React.createElement('div', {className: 'navigation'}, 'Hello World!')            );    }});

可以点此测试示例 - 我使用的是浏览器JSX解释器(这并不推荐 但是为了JSFiddle)。

运行了!JSX解释了你使用 React.createElement 生成的所有DOM节点 生成了节点类型、参数和内容。你可以不用JSX 但这意味着你必须要手动写 React.createElement 以外的所有DOM节点。无数例子告诉我要使用JSX。

你一定很疑惑我为什么在DOM上使用 className 来代替 class。这是因为 class 是Javascript的保留词。当JSX解释你的代码时就会改变这节点上的所有属性到一个对象上 但你不能把对象当作属性!

将变量用在属性上

如果你想动态改变组件的样式类(或者任何其他的属性值),你可以使用变量. 不过你不能只是传进去一个变量名称 你还要将其用一对大括弧包起来, 这样JSX就能知道这是一个外部的变量了.

var ExampleComponent = React.createClass({render: function () {  var navigationClass = 'navigation';  return (    <div className={ navigationClass }>      Hello World!    </div>    );}});

你可以在这儿看到这个功能.

初始的渲染器

当你最开始要渲染一个React组件时 你需要告诉React是要渲染什么组件 还要制定一个现有的DOM节点以表示在哪儿渲染这个组件. 为此你会要使用React.render函数.

var ExampleComponent = React.createClass({render: function () {  return (    <div className="navigation">      Hello World!    </div>    );}});React.render(<ExampleContent />, document.body);

他将会在body节点上渲染组件——简单吧! 从此你就可以像通常那样调用其他的组件了, 或者如果你希望的话 可以使用render函数许多次, 如果你不想使用React来渲染整个页面 但仍然想要使用多个组件.

一个组件的基础

组件可以拥有其自己的"状态"。这使能够重复多次使用相同的组件但却让它们看起来完全不同 因为每个组件实例的状态是唯一的。

当你通过传递属性到一个组件时这些被称为属性。不要只局限于 HTML 属性 你可以传递任何你想传递的东西 并在组件内通过 this.props 访问它。这样使得能够重复使用相同的组件但传递一组不同的属性 比如组件的"配置"。

属性

根据前面"Hello World!"的例子 在 HTML 节点上有 className 的属性。在组件内部 可以使用 this.props.classname 访问这个值 但正如前面所述 你可以传递任何你喜欢的内容。对的下拉来说 需要将导航配置为对象 组件将使用它作为要渲染的配置。让开始吧—

var navigationConfig = [];var Navigation = React.createClass({  render: function () {    return (      <div className="navigation">      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

如果现在能访问 this.props.config 的话 会受到一个空数组(navigationConfig 的值)。在进入到真正导航的编码前先让说明一下状态。

状态

如之前所讨论的 每一个组件都有其自己的"状态"。当要使用状态时 你要定义初始状态 让后才可以使用 this.setState 来更新状态。无论何时状态得到了更新 组件都会再一次调用 render 函数 拿新的值去替换或者改变之前渲染的值。这就是虚拟 DOM 的奥义 – 计算差异的算法实在 React 内部完成的 因此不用去依赖 DOM 的更新了(因为 DOM 很慢)。React 会计算出差异并产生一堆指令的集合 (例如 加入向"navigation__link"加入"active"类 或者移除一个节点) 并在 DOM 上执行它们。

使用导航 将下拉菜单打开保持在状态中。为此 添加一个 getInitialState 函数到类配置对象上 并返回带有想要的初始状态的对象。

var navigationConfig = [];var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  render: function () {    return (      <div className="navigation">      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

你会发现将其设置成了-1。当准备去打开一个下拉菜单时 将使用状态里面的配置数组中的导航项的位置 而由于数组索引开始于0 就得使用 -1 来表示还没有指向一个导航项。

可以使用 this.state 来访问状态 因此如果去观察 atthis.state.openDropdown 应该会有 -1 被返回。

组件的生命周期

每个组件都有其"生命周期"—这是一系列你可以在组件配置里定义的函数 它们将在部件生命周期内被调用。已经看过了 getinitialstate 一它只被调用一次 在组件被挂载时调用。

componentWillMount

当组件要被挂载时这个函数被调用。这意味着可以在此运行组件功能必须的代码。为由于 render 在组件生命周期里被多次调用 一般会把只需要执行一次的代码放在这里 比如 XHR 请求。

var ExampleComponent = React.createClass({  componentWillMount: function () {    // xhr request here to get data  },  render: function () {    // this gets called many times in a components life    return (      <div>        Hello World!      </div>      );  }});

componentDidMount

一旦你的组件已经运行了 render 函数 并实际将组件渲染到了 DOM 中 componentDidMount 就会被调用。可以在这儿做任何需要做的 DOM 操作 已经任何需要依赖于组件已经实际存在于 DOM 之后才能做的事情, 例如渲染一个图表。你可以通过调用 this.getDOMNode 在内部访问到 DOM 节点。

var ExampleComponent = React.createClass({  componentDidMount: function () {    var node = this.getDOMNode();    // render a chart on the DOM node  },  render: function () {    return (      <div>        Hello World!      </div>      );  }});

componentWillUnmount

如果你准备吧组件从 DOM 移除时 这个函数将会被调用。这让可以在组件背后进行清理 比如移除任何已经绑定的事件监听器。如果没有在自身背后做清理 而当其中一个事件被触发时 就会尝试去计算一个没有载入的组件 React 就会抛出一个错误。

var ExampleComponent = React.createClass({  componentWillUnmount: function () {    // unbind any event listeners specific to this component  },  render: function () {    return (      <div>        Hello World!      </div>      );  }});

组件方法

React 也为组件提供了更方便工作的方法。它们会在组件的创建过程中被调用到。例如getInitialState 能让定义一个默认的状态 这样不用担心在代码里面对状态项目是否存在做进一步的检查了。

getDefaultProps

当创建组件时 可以按照的想法为组件的属性定义默认值。这意味着在调用组件时 如果给这些属性设置值 组件会有一个默认的"配置" 而就不用操心在下一行代码中检查属性这类事情了。

当你定义了组件时 这些默认的属性会被缓存起来 因而针对每个组件的实例它们都是一样的 并且不能被改变。对于导航组件 将配置指定为一个空的数组 因而就算没有传入配置 render 方法内也不会发生错误。

var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  getDefaultProps: function () {    return {      config: []    }  },  render: function () {    return (      <div className="navigation">      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

propTypes

也可以随意为每一个属性指定类型。这对于检查和处理属性的意外赋值非常有用。如下面的dropdown 指定只有数组才能放入配置。

var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  getDefaultProps: function () {    return {      config: []    }  },  propTypes: {    config: React.PropTypes.array  },  render: function () {    return (      <div className="navigation">      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

mixins

也可以在组件中加入 mixins。这是一个独立于 React 的基本组件(只是一个对象类型的配置)。这意味着 如果有两个功能类似的组件 就可以共享一个配置了(如果初始状态相同)。可以抽象化地在 mixin 中建立一个方法 这样就不用把相同的代码写上两次了。

var ExampleMixin = {  componentDidMount: function () {    // bind some event listeners here  },  componentWillUnmount: function () {    // unbind those events here!  }};var ExampleComponent = React.createClass({  mixins: [ExampleMixin],  render: function () {    return (      <div>        Hello World!      </div>      );  }});var AnotherComponent = React.createClass({  mixins: [ExampleMixin],  render: function () {    return (      <div>        Hello World!      </div>      );  }});

这样全部组件都有一样的 componentDidMount 和 componentWillUnmount 方法了 保存重写的代码。无论如何 你不能 override(覆盖)这些属性 如果这个属性是在mixin里设置的 它在这个组件中是不可覆盖的。

遍历循环

当有一个包含对象的数组 如何循环这个数组并渲染每一个对象到列表项中?JSX 允许你在任意 Javascript 文件中使用它 你可以映射这个数组并返回 JSX 然后使用 React 去渲染。

var navigationConfig = [  {    href: 'http://ryanclark.me',    text: 'My Website'  }];var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  getDefaultProps: function () {    return {      config: []    }  },  propTypes: {    config: React.PropTypes.array  },  render: function () {    var config = this.props.config;    var items = config.map(function (item) {      return (        <li className="navigation__item">          <a className="navigation__link" href={ item.href }>            { item.text }          </a>        </li>        );    });    return (      <div className="navigation">        { items }      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

在 JSFilddle 中随意使用 navigationConfigin

导航配置由数组和对象组成 包括一个指向超链接的 href 属性和一个用于显示的 text 属性。当映射的时候 它会一个个依次通过这个数组去取得对象。可以访问 href 和 text 并在 HTML 中使用。当返回列表时 这个数组里的列表项都将被替换 所以把它放入 React 中处理时它将知道怎么去渲染了!

混编

到目前位置 已经做到了所有下拉列表的展开。需要知道被下来的项目是哪个 将使用 .children 属性去遍历的 navigationConfig 数组。接下来 可以通过循环来操作下拉的子元素条目。

var navigationConfig = [  {    href: 'http://ryanclark.me',    text: 'My Website',    children: [      {        href: 'http://ryanclark.me/how-angularjs-implements-dirty-checking/',        text: 'Angular Dirty Checking'      },      {        href: 'http://ryanclark.me/getting-started-with-react/',        text: 'React'      }    ]  }];var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  getDefaultProps: function () {    return {      config: []    }  },  propTypes: {    config: React.PropTypes.array  },  render: function () {    var config = this.props.config;    var items = config.map(function (item) {      var children, dropdown;      if (item.children) {        children = item.children.map(function (child) {          return (            <li className="navigation__dropdown__item">              <a className="navigation__dropdown__link" href={ child.href }>                { child.text }              </a>            </li>          );        });        dropdown = (          <ul className="navigation__dropdown">            { children }          </ul>        );      }      return (        <li className="navigation__item">          <a className="navigation__link" href={ item.href }>            { item.text }          </a>          { dropdown }        </li>        );    });    return (      <div className="navigation">        { items }      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

实例在这里 - 但是还是能看见下来条目 尽管将 openDropdown 设置成为了 -1 。

可以通过在组件中访问 this.state  来判断下拉是否被打开了 并且 可以为其添加一个新的 css 样式 class 来展现鼠标 hover 的效果。

var navigationConfig = [  {    href: 'http://ryanclark.me',    text: 'My Website',    children: [      {        href: 'http://ryanclark.me/how-angularjs-implements-dirty-checking/',        text: 'Angular Dirty Checking'      },      {        href: 'http://ryanclark.me/getting-started-with-react/',        text: 'React'      }    ]  }];var Navigation = React.createClass({  getInitialState: function () {    return {      openDropdown: -1    };  },  getDefaultProps: function () {    return {      config: []    }  },  openDropdown: function (id) {    this.setState({      openDropdown: id    });  },  closeDropdown: function () {    this.setState({      openDropdown: -1    });  },  propTypes: {    config: React.PropTypes.array  },  render: function () {    var config = this.props.config;    var items = config.map(function (item, index) {      var children, dropdown;      if (item.children) {        children = item.children.map(function (child) {          return (            <li className="navigation__dropdown__item">              <a className="navigation__dropdown__link" href={ child.href }>                { child.text }              </a>            </li>          );        });        var dropdownClass = 'navigation__dropdown';        if (this.state.openDropdown === index) {          dropdownClass += ' navigation__dropdown--open';        }        console.log(this.state.openDropdown, index);        dropdown = (          <ul className={ dropdownClass }>            { children }          </ul>        );      }      return (        <li className="navigation__item" onMouseOut={ this.closeDropdown } onMouseOver={ this.openDropdown.bind(this, index) }>          <a className="navigation__link" href={ item.href }>            { item.text }          </a>          { dropdown }        </li>        );    }, this);    return (      <div className="navigation">        { items }      </div>      );  }});React.render(<Navigation config={ navigationConfig } />, document.body);

在这里看实例 – 鼠标划过"My Website" 下拉即会展现。

在前面 我已经给每个列表项添加了鼠标事件。如你所见 我用的是 .bind (绑定) 调用 而非其它的方式调用 – 这是因为 当用户的鼠标划出元素区域 并不用关注光标在哪里 所有需要知晓的是 将下拉关闭掉 所以可以将它的值设置成为-1。但是 需要知晓的是当用户鼠标划入的时候哪个元素被下拉展开了 所以需要知道该参数(元素的索引)。 使用绑定的方式去调用而非简单的透过函数(function)去调用是因为需要通过 React 去调用。如果直接调用 那就需要一直调用 而不是在事件中调用他。

现在可以添加很多的条目到 navigationConfig 当中 而且也可以给他添加样式到下来功能当中。查看实例

总结

React 是一个简洁方便易用而又强大的框架。它简洁的 API 可以让人快速的创建自己的组件 而不去理会复杂的 dom 也不用做某些特别负责的操作 特别是在重复的列表项等上面表现不错。

来源:http://www.oschina.net/translate/getting-started-with-react