React系列之 React入门

原文地址: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 来解救你~

相关文章

导入moment 使用方式 年月日,时分秒 星期几 相对时间 7天后 2小时后 明天 将毫秒转换成年月日
@ 一、前言 为什么介绍redux-actions呢? 第一次见到主要是接手公司原有的项目,发现有之前的大佬在处理...
十大React Hook库 原文地址:https://dev.to/bornfightcompany/top-10-react-hook-libraries-4065 原文...
React生命周期 React的生命周期从广义上分为挂载、渲染、卸载三个阶段,在React的整个生命周期中提供很...
React虚拟DOM的理解 Virtual DOM是一棵以JavaScript对象作为基础的树,每一个节点可以将其称为VNode,用...
React中JSX的理解 JSX是快速生成react元素的一种语法,实际是React.createElement(component, props, ....