Node.js从入门到实战(六)React一页纸总结

一、React

React是一个JavaScript库,是由FaceBook和Instagram开发的,主要用于用户创建图形化界面。

由于 React 的设计思想极其独特,属于革命性创新,性能出众,代码逻辑却非常简单。所以,越来越多的人开始关注和使用,认为它可能是将来 Web 开发的主流工具。
这个项目本身也越滚越大,从最早的UI引擎变成了一整套前后端通吃的 Web App 解决方案。衍生的 React Native 项目,目标更是宏伟,希望用写 Web App 的方式去写 Native App。如果能够实现,整个互联网行业都会被颠覆,因为同一组人只需要写一次 UI ,就能同时运行在服务器、浏览器和手机。

二、React组件

React 的核心思想是:封装组件。各个组件维护自己的状态和 UI,当状态变更,自动重新渲染整个组件。基于这种方式的一个直观感受就是我们不再需要不厌其烦地来回查找某个 DOM 元素,然后操作 DOM 去更改 UI。
React 大体包含下面这些概念:

  1. 组件
  2. JSX
  3. Virtual DOM
  4. Data Flow
这里通过一个简单的组件来快速了解这些概念,以及建立起对 React 的一个总体认识。

  1. import React, { Component } from 'react';
  2. import { render } from 'react-dom';
  3. class HelloMessage extends Component {
  4. render() {
  5. return <div>Hello {this.props.name}</div>;
  6. }
  7. }
  8. // 加载组件到 DOM 元素 mountNode
  9. render( <HelloMessage name="John" />, mountNode);

组件

React 应用都是构建在组件之上。上面的 HelloMessage 就是一个 React 构建的组件,最后一句 render 会把这个组件显示到页面上的某个元素 mountNode 里面,显示的内容就是 <div>Hello John</div>。
props 是组件包含的两个核心概念之一,另一个是 state。可以把 props 看作是组件的配置属性,在组件内部是不变的,只是在调用这个组件的时候传入不同的属性(比如这里的 name)来定制显示这个组件。

JSX

从上面的代码可以看到将 HTML 直接嵌入了 JS 代码里面,这个就是 React 提出的一种叫 JSX 的语法,这应该是最开始接触 React 最不能接受的设定之一,因为前端被“表现和逻辑层分离”这种思想“洗脑”太久了。但实际上组件的 HTML 是组成一个组件不可分割的一部分,能够将 HTML 封装起来才是组件的完全体,React 发明了JSX 让 JS 支持嵌入 HTML 不得不说是一种非常聪明的做法,让前端实现真正意义上的组件化成为了可能。

Virtual DOM

当组件状态 state 有更改的时候,React 会自动调用组件的 render 方法重新渲染整个组件的 UI。
当然如果真的这样大面积的操作 DOM,性能会是一个很大的问题,所以 React 实现了一个Virtual DOM,组件 DOM 结构就是映射到这个 Virtual DOM 上,React 在这个 Virtual DOM 上实现了一个diff 算法,当要重新渲染组件的时候,会通过 diff 寻找到要变更的 DOM 节点,再把这个修改更新到浏览器实际的 DOM 节点上,所以实际上不是真的渲染整个 DOM 树。这个 Virtual DOM 是一个纯粹的 JS 数据结构,所以性能会比原生 DOM 快很多。

Data Flow

“单向数据绑定”是 React 推崇的一种应用架构的方式。当应用足够复杂时才能体会到它的好处,虽然在一般应用场景下你可能不会意识到它的存在,也不会影响你开始使用 React,你只要先知道有这么个概念。
使用 React 的网页源码,结构大致如下。

  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <meta charset="utf-8">
  5. <title>Hello world! </title>
  6. <script src = "../../build/react.js"> </script>
  7. <script src = "../../build/react-dom.js"> </script>
  8. <script src = "../../build/browser.min.js"> </script>
  9. </head>
  10. <body>
  11. <div id = "example"> </div>
  12. <script type="text/babel">
  13. ReactDOM.render(
  14. <h1>Hello,World!</h1>,
  15. document.getElementById( 'example')
  16. );
  17. </script>
  18. </body>
  19. </html>
上面代码有两个地方需要注意。首先,最后一个 <script> 标签的 type 属性为 text/babel  。这是因为 React 独有的  JSX 语法 ,跟 JavaScript 不兼容。凡是使用 JSX 的地方,都要加上 type="text/babel" 。
其次,上面代码一共用了三个库: react.js 、react-dom.js 和 Browser.js ,它们必须首先加载。其中, react.js  是 React 的 核心库 ,react-dom.js 是提供 与 DOM 相关的功能 ,Browser.js 的作用是 将 JSX 语法转为 JavaScript 语法 ,这一步很消耗时间,实际上线的时候,应该将它放到服务器完成。
$ babel src --out-dir build
JSX的基本语法规则:遇到 HTML标签(以<开头) ,就用HTML规则解析;遇到 代码块(以{开头) ,就用JavaScript规则解析。上面命令可以将 src 子目录的 js 文件进行语法转换,转码后的文件全部放在  build 子目录

三、React组件

3.1 ReactDOM.render()

ReactDOM.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点

  1. ReactDOM.render(
  2. <h1>Hello, world!</h1>,
  3. document.getElementById( 'example')
  4. );
上面代码将一个 h1 标题,插入 example 节点(查看 demo01),运行结果如下。



3.2 props和state

组件有两个核心概念:

  1. props
  2. state
一个组件就是通过这两个属性的值在 render 方法里面生成这个组件对应的 HTML 结构。

props 就是组件的属性,由外部通过 JSX 属性传入设置,一旦初始设置完成,就可以认为 this.props 是不可更改的,所以不要轻易更改设置 this.props 里面的值(虽然对于一个 JS 对象你可以做任何事)。
state 是组件的当前状态,可以把组件简单看成一个“状态机”,根据状态 state 呈现不同的 UI 展示。一旦状态(数据)更改,组件就会自动调用 render 重新渲染 UI,这个更改的动作会通过 this.setState 方法来触发。

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

  1. var NotesList = React.createClass({
  2. render: function() {
  3. return (
  4. <ol>
  5. {
  6. React.Children.map(this.props.children, function (child) {
  7. return <li>{child} </li>;
  8. })
  9. }
  10. </ol>
  11. );
  12. }
  13. });
  14. ReactDOM.render(
  15. <NotesList>
  16. <span>hello </span>
  17. <span>world </span>
  18. </NotesList>,
  19. document.body
  20. );
上面代码的 NoteList 组件有两个  span 子节点 ,它们都可以通过 this.props.children 读取,运行结果如下。
  1. 1. hello
  2. 2. world
这里需要注意, this.props.children 的值有三种可能:如果当前组件没有子节点,它就是  undefined  ;如果有一个子节点,数据类型是 object  ;如果有多个子节点,数据类型就是  array  。所以,处理 this.props.children 的时候要小心。React 提供一个工具方法 React.Children 来处理 this.props.children 。我们可以用 React.Children.map 来遍历子节点,而不用担心 this.props.children 的数据类型是 undefined 还是 object。

3.3 事件处理

React 的一大创新,就是将组件看成是一个状态机,一开始有一个初始状态,然后用户互动,导致状态变化,从而触发重新渲染 UI,如下的例子显示了React中事件绑定和处理的机制: 

  1. import React, { Component } from 'react';
  2. import { render } from 'react-dom';
  3. class LikeButton extends Component {
  4. constructor(props) {
  5. super(props);
  6. this.state = { liked: false };
  7. }
  8. handleClick(e) {
  9. this.setState({ liked: !this.state.liked });
  10. }
  11. render() {
  12. const text = this.state.liked ? 'like' : 'haven\'t liked';
  13. return (
  14. <p onClick={this.handleClick.bind(this)}>
  15. You {text} this. Click to toggle.
  16. </p>
  17. );
  18. }
  19. }
  20. render(
  21. <LikeButton />,
  22. document.getElementById('example')
  23. );
上面代码是一个 LikeButton 组件,它的 getInitialState 方法用于定义初始状态,也就是一个对象,这个对象可以通过  this.state 属性读取。当用户点击组件,导致状态变化,this.setState 方法就修改状态值,每次修改以后,自动调用  this.render 方法,再次渲染组件。
由于 this.props 和 this.state 都用于描述组件的特性,可能会产生混淆。一个简单的区分方法是,this.props 表示那些一旦定义,就不再改变的特性,而 this.state 是会随着用户互动而产生变化的特性。

3.4 组件的生命周期

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

  • Mounting:已插入真实 DOM
  • Updating:正在被重新渲染
  • Unmounting:已移出真实 DOM
React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。

  1. componentWillMount()
  2. componentDidMount()
  3. componentWillUpdate(object nextProps, object nextState)
  4. componentDidUpdate(object prevProps, object prevState)
  5. componentWillUnmount()

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

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

如下是状态处理函数的使用实例,即一个100ms定时器:

  1. var Hello = React.createClass({
  2. getInitialState: function () {
  3. return {
  4. opacity: 1.0
  5. };
  6. },
  7. componentDidMount: function () {
  8. this.timer = setInterval( function () {
  9. var opacity = this.state.opacity;
  10. opacity -= .05;
  11. if (opacity < 0.1) {
  12. opacity = 1.0;
  13. }
  14. this.setState({
  15. opacity: opacity
  16. });
  17. }.bind( this), 100);
  18. },
  19. render: function () {
  20. return (
  21. <div style={{opacity: this.state.opacity}}>
  22. Hello {this.props.name}
  23. </div>
  24. );
  25. }
  26. });
  27. ReactDOM.render(
  28. <Hello name="world"/>,
  29. document.body
  30. );
上面代码在hello组件加载以后,通过  componentDidMount  方法设置一个定时器,每隔100毫秒,就 重新设置组件的透明度 ,从而引发重新渲染。

3.5 组件的引用

ReactDOM.render 组件返回的是什么?
它会返回对组件的引用也就是组件实例(对于无状态状态组件来说返回 null),注意 JSX 返回的不是组件实例,它只是一个ReactElement对象(还记得我们用纯 JS 来构建 JSX 的方式吗),比如这种:

  1. // A ReactElement
  2. const myComponent = <MyComponent />
  3. // render
  4. const myComponentInstance = ReactDOM.render(myComponent, mountNode);
  5. myComponentInstance.doSomething();

findDOMNode()
当组件加载到页面上之后(mounted),你都可以通过 react-dom 提供的  findDOMNode () 方法拿到组件对应的 DOM 元素。

  1. import { findDOMNode } from 'react-dom';
  2. // Inside Component class
  3. componentDidMound() {
  4. const el = findDOMNode( this);
  5. }
Refs

另外一种方式就是通过在要引用的 DOM 元素上面设置一个 ref 属性指定一个名称,然后通过 this.refs.name 来访问对应的 DOM 元素。比如有一种情况是必须直接操作 DOM 来实现的,你希望一个 <input/> 元素在你清空它的值时 focus,你没法仅仅靠 state 来实现这个功能。

  1. class App extends Component {
  2. constructor() {
  3. return { userInput: '' };
  4. }
  5. handleChange(e) {
  6. this.setState({ userInput: e.target.value });
  7. }
  8. clearAndFocusInput() {
  9. this.setState({ userInput: '' }, () => {
  10. this.refs.theInput.focus();
  11. });
  12. }
  13. render() {
  14. return (
  15. <div>
  16. <div onClick={this.clearAndFocusInput.bind(this)}>
  17. Click to Focus and Reset
  18. </div>
  19. <input
  20. ref= "theInput"
  21. value= {this.state.userInput}
  22. onChange= {this.handleChange.bind(this)}
  23. />
  24. </div>
  25. );
  26. }
  27. }
如果 ref 是设置在原生 HTML 元素上,它拿到的就是 DOM 元素 ,如果设置在自定义组件上,它拿到的就是 组件实例 ,这时候就需要通过  findDOMNode  来拿到组件的 DOM 元素。
因为无状态组件没有实例,所以 ref 不能设置在无状态组件上,一般来说这没什么问题,因为无状态组件没有实例方法,不需要 ref 去拿实例调用相关的方法,但是如果想要拿无状态组件的 DOM 元素的时候,就需要用一个状态组件封装一层,然后通过 ref 和 findDOMNode 去获取。

3.6 组件间通信

父子组件间通信

这种情况下很简单,就是通过 props 属性传递,在父组件给子组件设置 props,然后子组件就可以通过 props 访问到父组件的数据/方法,这样就搭建起了父子组件间通信的桥梁。

  1. import React, { Component } from 'react';
  2. import { render } from 'react-dom';
  3. class GroceryList extends Component {
  4. handleClick(i) {
  5. console.log( 'You clicked: ' + this.props.items[i]);
  6. }
  7. render() {
  8. return (
  9. <div>
  10. {this.props.items.map((item, i) => {
  11. return (
  12. <div onClick={this.handleClick.bind(this, i)} key={i}>{item} </div>
  13. );
  14. })}
  15. </div>
  16. );
  17. }
  18. }
  19. render(
  20. <GroceryList items={['Apple', 'Banana', 'Cranberry']} />, mountNode
  21. );
div 可以看作一个子组件,指定它的 onClick 事件调用父组件的方法。父组件访问子组件?用 refs即可。

非父子组件间的通信 
使用全局事件 Pub/Sub 模式,在 componentDidMount 里面订阅事件,在componentWillUnmount 里面取消订阅,当收到事件触发的时候调用setState 更新 UI。

3.7 Ajax请求

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

  1. var UserGist = React.createClass({
  2. getInitialState: function() {
  3. return {
  4. username: '',
  5. lastGistUrl: ''
  6. }
  7. },
  8. componentDidMount: function(){
  9. $.get( this.props.source, function(result){
  10. var lastGist = result[ 0];
  11. if ( this.isMounted()) {
  12. this.setState({
  13. username:lastGist.owner.login,
  14. lastGistUrl:lastGist.html_url
  15. }
  16. );
  17. }
  18. }.bind( this));
  19. },
  20. render: function() {
  21. return (
  22. <div>
  23. {this.state.username}'s last gist is
  24. <a href={this.state.lastGistUrl}>here </a>
  25. </div>
  26. );
  27. }
  28. });
  29. ReactDOM.render(
  30. <UserGist source = "https://api.github.com/users/octocat/gists"/>,
  31. document.getElementById('example')
  32. );
上面代码使用 jQuery 完成 Ajax 请求,在DidMout中使用$.get实现HTTP请求。

猜你喜欢

转载自blog.csdn.net/plpldog/article/details/80869082