IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    React系列之 React入门

    六楼发表于 2016-09-18 15:51:03
    love 0

    原文地址:https://gmiam.com/post/react-...

    React 是一个 JS 库,主要是通过操作数据的方式去操纵 DOM,为什么要重造轮子呢,因为 FB 觉的目前市面上的 MV* 框架对于创建大型应用程序不够直观,不能满足需求,所以诞生了 React。

    React 现在官方的介绍是 Declarative、Component-Based、Learn Once, Write Anywhere,其实开始推出时主要的特色是 Virtual DOM,因为 DOM 操作总是很慢的,而 JS 的性能日趋向上,所以 React 内部用 JS 维护一颗 DOM 树,每次数据变了从新生成一颗树与之前的做对比,把实际变化的地方应用到真实的 DOM 上。其实说它性能高,只不过是用 JS 的方式计算出最小的 DOM 操作,所以性能就上来了。

    说到这里我们实际操作下吧,这里假设你熟悉 node、babel、webpack 方式,当然你也可以选择你喜好的方式 传送门

    首先创建目录结构

    react-demo
    
        .babelrc
    
        index.html
    
        src
            app.js

    index.html

    <!doctype html>
    <html lang="en">
      <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
        <title>React App</title>
      </head>
      <body>
        <div id="app"></div>
        <script src="bundle.js"></script>
      </body>
    </html>
    

    app.js

    var React = require( 'react' )
    var ReactDOM = require( 'react-dom' )
    
    var HelloMessage = React.createClass( {
        render: function () {
            return <div>Hello {this.props.name}</div>
        }
    })
    
    ReactDOM.render( <HelloMessage name="John" />, document.getElementById( 'app' ) )
    

    .babelrc

    { "presets": ["es2015","react"] }

    安装依赖 npm install --save react react-dom babel-preset-react babel-loader babel-core

    编译监听 webpack src/app.js bundle.js -w --module-bind 'js=babel'

    打开 index.html 查看效果

    先说下 jsx 语法,React 让你不需要再写 html 拼接字符等操作,而是直接写 html,js 处理放到 { } 里书写,官方提供 jsx 语法非必要,也可以脱离写纯 js 的,如上面的经过编译后

    "use strict";
    
    var HelloMessage = React.createClass({
      displayName: "HelloMessage",
    
      render: function render() {
        return React.createElement(
          "div",
          null,
          "Hello ",
          this.props.name
        );
      }
    });
    
    ReactDOM.render(React.createElement(HelloMessage, { name: "John" }), document.getElementById( 'app' ));
    

    但是可以看出这么麻烦没人去手写的

    再来说下组件,React 的概念就是给应用分层,创建一个个组件,最后拼出一个页面,组件方便后期的维护、扩展、以及再重用,随着组件的越多后面写的代码越少,来个例子

    var Avatar = React.createClass({
      render: function() {
        return (
          <div>
            <PagePic pagename={this.props.pagename} />
            <PageLink pagename={this.props.pagename} />
          </div>
        );
      }
    });
    
    var PagePic = React.createClass({
      render: function() {
        return (
          <img src={'https://graph.facebook.com/' + this.props.pagename + '/picture'} />
        );
      }
    });
    
    var PageLink = React.createClass({
      render: function() {
        return (
          <a href={'https://www.facebook.com/' + this.props.pagename}>
            {this.props.pagename}
          </a>
        );
      }
    });
    
    ReactDOM.render(
      <Avatar pagename="Engineering" />,
      document.getElementById('app')
    );

    可以看到组件要提供自己的 render 方法,组件可以相互嵌套,数据通过 this.props 单向传递

    同时需要注意

    属性 class 要写成 className,for 写成 htmlFor,因为它们是 js 的保留字

    对于render 返回的内容只能有一个顶级标签,如果标签超过多行要用 ( ) 包含

    关于 props 不要去改变它,会导致一些不可预知的问题,另外官方推荐用 es6 的 ... 操作符去挂载属性

    var props = { foo: 'default', bar:'bar' };
    var component = <Component {...props} foo={'override'} />;
    console.log(component.props.bar); // 'bar'
    console.log(component.props.foo); // 'override'

    这里有个特殊属性 this.props.children,来个例子

    var NotesList = React.createClass({
      propTypes: {
            children: React.PropTypes.array.isRequired,
      },
      render: function() {
        return (
          <ol>
          {
            React.Children.map(this.props.children, function (child) {
              return <li>{child}</li>;
            })
          }
          </ol>
        );
      }
    });
    
    ReactDOM.render(
      <NotesList>
        <span>hello</span>
        <span>world</span>
      </NotesList>, document.getElementById('app')
    );
    

    同时可以看到这里提供了 propTypes 可以给属性做检查,验证说明 children 必须提供且是一个数组(多个),更多的类型验证可以 看这里

    前面创建组件都是通过 React.createClass ,可以通过 es6 class 语法

    class HelloMessage extends React.Component {
      render() {
        return <div>Hello {this.props.name}</div>;
      }
    }
    ReactDOM.render(<HelloMessage name="Sebastian" />, document.getElementById('app'));

    还有 Stateless Functions 方式

    function HelloMessage(props) {
      return <div>Hello {props.name}</div>;
    }
    ReactDOM.render(<HelloMessage name="Sebastian" />, document.getElementById('app'));

    官方推荐尽量写 stateless functions ,因为未来会优化这些来避免无用的检查和内存分配

    下面看下如何写事件

    var Input = React.createClass({
      getInitialState: function() {
        return {value: 'Hello!'};
      },
      handleChange: function(event) {
        this.setState({value: event.target.value});
      },
      render: function () {
        var value = this.state.value;
        return (
          <div>
            <input type="text" value={value} onChange={this.handleChange} />
            <p>{value}</p>
          </div>
        );
      }
    });
    
    ReactDOM.render(<Input/>, document.getElementById('app'));
    

    骆驼式的 on 语法即可监听事件,事件是标准的跨浏览器的事件,虽然内联写法,但是是委托实现的~

    说到了事件交互可能就要设及获取真实的 dom 节点,React 通过 ref 设置,来个例子

    var React = require( 'react' )
    var ReactDOM = require( 'react-dom' )
    
    var MyComponent = React.createClass({
      handleClick: function() {
        this.refs['myinput'].focus()
      },
      render: function() {
        return (
          <div>
            <input type="text" ref="myinput" />
            <input
              type="button"
              value="Focus the text input"
              onClick={this.handleClick}
            />
          </div>
        );
      }
    });
    
    ReactDOM.render(
      <MyComponent />,
      document.getElementById('app')
    );

    ref 字符属性的方式未来会被废弃,官方推荐使用 ref callback 方式

    var MyComponent = React.createClass({
      handleClick: function() {
        if (this.myTextInput !== null) {
          this.myTextInput.focus();
        }
      },
      render: function() {
        return (
          <div>
            <input type="text" ref={(ref) => this.myTextInput = ref} />
            <input
              type="button"
              value="Focus the text input"
              onClick={this.handleClick}
            />
          </div>
        );
      }
    });
    
    ReactDOM.render(
      <MyComponent />,
      document.getElementById('app')
    );

    说到这里看下组件的生命周期与如何更新,还是来个例子

    var Timer = React.createClass({
      getInitialState: function() {
        return {secondsElapsed: 0};
      },
      tick: function() {
        this.setState({secondsElapsed: this.state.secondsElapsed + 1});
      },
      componentDidMount: function() {
        this.interval = setInterval(this.tick, 1000);
      },
      componentWillUnmount: function() {
        clearInterval(this.interval);
      },
      render: function() {
        return (
          <div>Seconds Elapsed: {this.state.secondsElapsed}</div>
        );
      }
    });
    
    ReactDOM.render(<Timer />,  document.getElementById('app'));

    生命周期有三个主要部分

    • Mounting 插入 dom

      • getInitialState()

      • componentWillMount()

      • componentDidMount ()

    • Updating 重新渲染

      • componentWillReceiveProps(object nextProps)

      • shouldComponentUpdate(object nextProps, object nextState)

      • componentWillUpdate(object nextProps, object nextState)

      • componentDidUpdate(object prevProps, object prevState)

    • Unmounting 移除 dom

      • componentWillUnmount()

    周期提供了 will 方法在事情发生之前调用, did 方法在事情法神之后调用,具体查看这里

    对于更新,上面的例子在组件 componentDidMount (插入 dom 后) hook 中定时更新组件的 state,state变更会导致 render 重新渲染页面

    对于这里说下性能问题,虽然虚拟dom计算过程很快,但是很多时候我们可以避免它的计算以更好的优化处理

    例如 一个组件的更新可能会导致它的子组件一起跟着更新,子组件很可能没有变化,但同样会进行一次diff运算,白白浪费了时间,所以 React 提供了 shouldComponentUpdate 钩子函数,默认是直接返回 true,也及是每次都运算比较,所以我们可以在这里优化,来个例子

    React.createClass({
      propTypes: {
        value: React.PropTypes.string.isRequired
      },
      shouldComponentUpdate: function(nextProps, nextState) {
          return this.props.value !== nextProps.value;
      },
      render: function() {
        return <div>{this.props.value}</div>;
      }
    });

    这里只有 value 变化的时候在重新渲染计算,否则直接跳过

    对于上面的浅对比,React 提供了通用解决方案 PureRenderMixin 扩展,应用 React 的 mixins 功能即可自动实现处理比对

    var PureRenderMixin = require('react-addons-pure-render-mixin');
    React.createClass({
      mixins: [PureRenderMixin],
    
      render: function() {
         return <div>{this.props.value}</div>;
      }
    });

    但是如果有深层结构,上面的处理可能不会按预期工作,例如

    //  this.props.value 的值为 { foo: 'bar' }
    // nextProps.value 的值为 { foo: 'bar' },
    // 但是对象的引用不同,导致不会相等
    this.props.value !== nextProps.value; // true

    而且如果我们不小心管理引用的话也会引发另一些问题,例如这个组件有一个父组件

    React.createClass({
      getInitialState: function() {
        return { value: { foo: 'bar' } };
      },
    
      onClick: function() {
        var value = this.state.value;
        value.foo += 'bar'; // ANTI-PATTERN!
        this.setState({ value: value });
      },
    
      render: function() {
        return (
          <div>
            <InnerComponent value={this.state.value} />
            <a onClick={this.onClick}>Click me</a>
          </div>
        );
      }
    });

    首先内部组件得到 { foo: 'bar' },点击后出发 value 更新 { foo: 'barbar' },触发 re-rendering 程序,内部组件将会得到 { foo: 'barbar' },但是 this.props.value 与 nextProps.value 指向同一个引用,导致任何时候比对都是 true,而导致页面不更新

    而且如果父组件应用 PureRenderMixin 的话,由于改动相同引用所以也会导致父组件的 re-rendering 不触发

    那最后该如何处理呢?请看下一篇 Immutable-js 来解救你~



沪ICP备19023445号-2号
友情链接