ReactJS基础

Starter

Using Directly

> bower install react react-dom babel
<div id="example"></div>

<script type="text/javascript" src="bower_components/react/react.js"></script>
<script type="text/javascript" src="bower_components/react/react-dom.js"></script>
<script type="text/javascript" src="bower_components/babel/browser.min.js"></script>
<script type="text/babel">
    // write your reactjs here
    ReactDOM.render(
      <h1>Hello, world!</h1>,
      document.getElementById('example')
    );
</script>

Using Builded JS Depend on npm

  1. 下载ReactJS依赖包:

     > npm install react react-dom --save
    
  2. 编写React:

     // ./src/xx.jsx
     var React=require("react");
     var ReactDOM=require("react-dom");
    
     ReactDOM.render(<h1>Hello, world!</h1>,document.getElementById('app'));
    
     var element = React.createElement('h1', null, 'Hello, world!');
     ReactDOM.render(element,document.getElementById('example'));
    
  3. 编译生成可直接在浏览器执行的JS (这里提供两种方法,参考下文)

    • Method1: Build By Browserify
    • Method2: Build By Webpack
  4. HTML页面中直接调用生成的JS文件:

     <div id="app" class="container"></div>
     <script type="text/javascript" src="xxx.js"></script>
    
  5. 可通过Gulp或Webpack插件实现Server热加载实时预览等功能,eg:
    • gulp插件: gulp-server-livereload
    • webpack插件:webpack-dev-server (还可结合使用react-hot高效react热加载功能)

Build by browserify

  • 下载依赖包:

      > npm install browserify -g
      > npm install babelify --save
      > npm install watchify -g
    

    babelify :合并模块时对 ES6 和 ES7 进行编译转换,同时还可以对 JSX 进行编译转换,也可使用 reactify

    watchify :监听,配合 browserify 实现实时合并 (是一个browserify的封装,配置同browserify)

  • 执行

    • browserify 合并模块

      > browserify ./src/app.jsx -t babelify -o ./dist/bundle.js
      
      //如果require的module的后缀不为.js,.json,需添加-extension参数,eg:
      > browserify ./src/app.jsx -t babelify -o ./dist/bundle.js --extension .jsx
      
    • watchify 监听

      > watchify ./src/app.jsx -t babelify --extension .jsx  -o ./dist/bundle.js -v
      
    • 可在package.json中配置browserify

      "browserify" : {
            "transform": [
                ["reactify", {"es6": true}]
            ],
            "extensions":[".jsx"]
        }
      
      > browserify xxx.jsx -o xxx.js
      > watchify xxx.jsx -o xxx.js
      
    • 可使用 gulp

      var gulp=require("gulp");
      var gutil=require("gulp-util");
      var source = require('vinyl-source-stream');
      var watchify=require("watchify");
      var browserify = require('browserify');
      var babelify=require("babelify");
      var argv=require("yargs").argv;
      
      function doBuild(filename){
        if(!filename)
            filename="app";
      
        var b=watchify(browserify({
            entries:["./src/"+filename+".jsx"],
            transform:[babelify],
            extensions:[".jsx"],
            debug: true,
            cache: {},
            packageCache: {},
            fullPaths: true
        }));
      
        function build(file){
            if(file)
                gutil.log('Recompiling ' + file);
            b.bundle()
            .on('error',gutil.log.bind(gutil,"Browserify Error"))
            .pipe(source(filename+".js"))
            .pipe(gulp.dest('./dist'));
        };
      
        build();
        b.on('update',build);
      }
      
      gulp.task('build',function(){
        var filename=argv.n || "app";
        doBuild(filename);
      })
      
      > gulp build
      

Build by webpack

  • 下载依赖包

      > npm install jsx-loader --save
    
  • 编写webpack.config.js

      //webpack.config.js
      module.exports = {
          entry: "./src/questionApp.jsx",
          output: {
              path: __dirname+"/dist",
              filename: "questionApp-webpack.js"
              publicPath:"/dist"
          },
          resolve: {
              extensions: ['','.js','.jsx']
          },
          module: {
              loaders: [
                  //{ test: /\.jsx$/, loader: "babel-loader!jsx-loader?harmony" }
                  { test: /\.jsx$/, loader: "jsx" }
              ]
          }
      };
    
  • 执行

      > webpack
    

    或通过webpack-dev-server Visit http://127.0.0.1:8080 with your browser.(先下载包: npm install webpack-dev-server -g

      > webpack-dev-server
      //启动热加载
      > webpack-dev-server --hot --inline
    

Basic Features

Component

var reactComponent=React.createClass({
    ...
    render:function(){
        ...
        return ...
    },
    ...
});

Element

var reactElement = React.createElement(type, props, children);

或通过Factory创建Element:

var tag = React.createFactory(type);
var reactElement = tag(props, children);

使用示例:

var element=React.createElement('div', {className:'my-div'}, 'Hello, world!');

//或:
var div = React.createFactory('div');
var element = div({ className: 'my-div' },'Hello, world!');

Render

ReactDOM.render(reactElement,domContainerNode,callback);

使用示例:

var Question=require("./components/question");
var propOpts={...};

//use createElement:
var element=React.createElement(Question, propOpts);
ReactDOM.render(element,document.getElementById('app'));

//or use createFactory:
var QuestionFactory=React.createFactory(Question);
ReactDOM.render(QuestionFactory(propOpts),document.getElementById('app'));

//or use JSX:
ReactDOM.render(<Question {...propOpts} />,document.getElementById('app'));
// ./components/question.jsx:
var React=require("react");
module.exports=React.createClass({
    ...
    render:function(){
        ...
        //use JSX:
        return (<div className='my-div'>Question System</div>);
        //or use createElement:
        //return React.createElement('div',{className:my-div},'Question System');
    },
    ...
})

LifeCycle

Component Lifecycle :

  • Mounting :
    • void componentWillMount()
    • void componentDidMount()
  • Updating :
    • receive new props
      • void componentWillReceiveProps (object nextProps)
    • boolean shouldComponentUpdate(object nextProps, object nextState)
    • void componentWillUpdate( object nextProps, object nextState)
    • void componentDidUpdate(object prevProps, object prevState)
  • UnMount :
    • void componentWillUnmount()

note: shouldComponentUpdate(nextProps,nextStates) & render() should in mixin or component (only in one place)

Initial (第一次 render ) :

  • -> getDefaultProps
  • -> getInitialState
  • -> componentWillMount
  • -> render
    • sub component (do same initial cycle)
  • -> componentDidmount
  • -> RenderFinished!

Update (第n次 render ):

  • -> shouldComponentUpdate(nextProps,nextState)
    • return false -- stop here
    • return true -- go to next
  • -> componentWillUpdate(nextProps,nextState)
  • -> render
    • Sub Component: componentWillReceiveProps(nextProps)
    • Sub Component: shouldComponentUpdate(nextProps,nextState)
    • Sub Component: componentWillUpdate(nextProps,nextState)
    • Sub Component: componentDidUpdate(prevProps,prevState)
  • -> componentDidUpdate(prevProps,prevState)

Initial (has Mixins):

  • -> getDefaultProps -- Mixins + Component (merge return) -- can't set same property
  • -> getInitialState -- Mixins + Component (merge return) -- can't set same property
  • -> Mixins: componentWillMount
  • -> componentWillMount
  • -> render -- in Mixins or Component
  • -> Mixins: componentDidMount
  • -> componentDidMount
  • -> RenderFinished!

update (has Mixins):

  • -> shouldComponentUpdate -- in Mixins or Component
  • -> Mixins: componentWillUpdate
  • -> componentWillUpdate
  • -> render -- in Mixins or Component
  • -> Mixins: componentDidUpdate
  • -> componentDidUpdate

Props

初始化props:

getDefaultProps:function(){
    return {...}
},

可通过定义propTypes限定props,eg:

propTypes: {
    value: React.PropTypes.object.isRequired,
    onChange: React.PropTypes.func.isRequired,
    onSubmit: React.PropTypes.func.isRequired,
},

使用时传入props参数:

<Tag {...opts} />
<Tag xxx='...' yyy={...} zzz=...  ... />

当传入新的props参数时,会触发Component的以下方法:

void componentWillReceiveProps (object nextProps)
boolean shouldComponentUpdate(object nextProps, object nextState)
void componentWillUpdate( object nextProps, object nextState)
void render()
void componentDidUpdate(object prevProps, object prevState)

注意

  • this.props.children是获取当前节点的全部子内容
  • this.props.xxx获取参数值(注意不可这样修改props)
  • Component间传递:
    • Father state/props to Children' props
    • refs

State

初始化state:

getInitialState:function(){
    return {...}
}

获取state值:

  • this.state.xxx

设置state值:

  • this.setState({...})
  • this.state.xxx=...;,还需调用this.forceUpdate()生效

当state变化时会触发以下方法:

boolean shouldComponentUpdate(object nextProps, object nextState)
void componentWillUpdate( object nextProps, object nextState)
void render()
void componentDidUpdate(object prevProps, object prevState)

Refs

设置ref(可以绑定到 render() 输出的任何组件上去),eg:

<input type="text" defaultValue={this.state.inputValue} ref="goodInput"/>

获取:

  • access the component instance: this.refs.xxx or this.refs[xxx]
  • access the DOM ReactDOM.findDOMNode(componentInstance)

eg:

//在function中抓取我們動態render的某個UI元素
this.refs.goodInput

this.refs.goodInput.value
this.refs.goodInput.focus()
ReactDOM.findDOMNode(this.refs.goodInput).focus();

Form

  • Interactive props: value,checked,selected
  • SyntheticEvent :React对Event进行了封装
  • Form Events: onChange, onInput,onClick, onSubmit,onReset,...

eg:

render:function(){
    return (
    <form onSubmit={this.submitHandler}>
        <input type="text" value={this.state.inputValue} onChange={this.inputHandler}/>
        <br/>
        <input type="radio" name="goodRadio" value="A" defaultChecked onChange={this.radioHandler}/>A
        <input type="radio" name="goodRadio" value="B" onChange={this.radioHandler}/>B
        <input type="radio" name="goodRadio" value="C" onChange={this.radioHandler}/>C
        <button type="reset" >Reset</button> <button type="submit">Submit</button>
    </form>
    )
},

submitHandler:function(e){
    // React对event进行了优化,若未使用则e填充值都为null
    console.log(e);
    console.log(e.target);
    console.log(e.type);
    console.log(this.state);
    e.preventDefault();
},
resetHandler:function(e){
    if(e) e.preventDefault();
    ReactDOM.findDOMNode(this.refs.regForm).reset();
},
inputHandler:function(e){
    this.setState({inputValue:e.target.value});
},
radioHandler:function(e){
    this.setState({radioValue:e.target.value});
},
...

Isomorphic

同构,可在client或server端渲染React对象,统一使用,提供渲染速度,利于SEO

使用 ReactDOMServer (in react-dom/server package)

  • string renderToString(ReactElement element)
  • string renderToStaticMarkup(ReactElement element) //doesn't create extra DOM attributes such as data-react-id, that React uses internally

一个简单示例

  1. messageBox.jsx 定义需要同构的React Component:

     var MessageBox = 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>
               <h1>This is Isomorphic Test:</h1>
             <input type="text" value={value} onChange={this.handleChange} />
             <p>Inputed:{value}</p>
           </div>
         );
       }
     });
     module.exports=MessageBox;
    
  2. app.jsx 实现Client端渲染:

     var MessageBox=require('messageBox.jsx');
     ReactDOM.render(<MessageBox/>,document.getElementById('app'));
    
  3. app.js 编译生成的Client端可执行的js文件

     > browserify app.jsx -t babelify --presets react -o app.js --extension .jsx
    

    生成的js如下:

     (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
     'use strict';
    
     var MessageBox = React.createClass({
       displayName: 'MessageBox',
    
       getInitialState: function getInitialState() {
         return { value: 'Hello!' };
       },
       handleChange: function handleChange(event) {
         this.setState({ value: event.target.value });
       },
       render: function render() {
         var value = this.state.value;
         return React.createElement(
           'div',
           null,
           React.createElement(
             'h1',
             null,
             'This is Isomorphic Test:'
           ),
           React.createElement('input', { type: 'text', value: value, onChange: this.handleChange }),
           React.createElement(
             'p',
             null,
             'Inputed:',
             value
           )
         );
       }
     });
    
     ReactDOM.render(React.createElement(MessageBox, null), document.getElementById('app'));
    
     },{}]},{},[1]);
    
  4. index.html 定义主页面,加载上面生成的Client的js文件app.js

     <!DOCTYPE html>
     <html lang="en">
     <head>
       <meta charset="UTF-8">
       <title>Hello React!</title>
     </head>
     <body>
    
       <div id="app">{content}</div>
    
       <script type="text/javascript" src="libs/react/react.js"></script>
       <script type="text/javascript" src="libs/react/react-dom.js"></script>
       <script type="text/javascript" src="app.js"></script>
    
     </body>
     </html>
    
  5. server.js 定义Server端响应渲染

     require("babel-core/register");
    
     var React=require("react");
     var ReactDOMServer=require("react-dom/server");
     var http=require("http");
     var fs=require("fs");
    
     var server=http.createServer(function(req,res){
         console.log(req.url);
         if(req.url=='/' || req.url=='/index.html'){
             var element = React.createElement(require("./messageBox.jsx"));
             var reactHtml = ReactDOMServer.renderToString(element);
    
             fs.readFile("./index.html",function(err,data){
                 if (err) throw err;
                 res.writeHead(200,{'Content-Type':'text/html'});
                   res.end(data.toString().replace("{content}",reactHtml));
             });
         }else if(req.url.startsWith("/libs")){
             fs.readFile("."+req.url,function(err,data){
                 if(err) throw err;
                 res.writeHead(200);
                   res.end(data);
             })
         }else{
             res.writeHead(400,{'Content-Type':'text/plain'});
             res.end("not found");
         }
     });
    
     server.listen(3000, 'localhost', function (err, result) {
       if (err) console.log(err);
       console.log('Listening at localhost:3000');
     });
    
  6. 执行node server启动服务查看效果

PS: 如果出现无法识别解析JSX的错误,请检查使用的babel-core版本,6和5的使用上会有些不同,具体可参考The Six Things You Need To Know About Babel 6

Use Extra JS in React

eg: Using JQuery in React

var $=require("jquery");
module.exports=React.createClass({
    componentDidMount:function(){
        //一般在这里进行初始化绑定dom
        //eg: $('#xxx').modal('hide');
    },
    ...
    //可在其他方法中调用操作
    //eg: $('#xxx').modal('show');
});

eg: Using highlight.js in React Issue on Github

var hljs =require("highlight.js");
var Pretty = React.createClass({
    componentDidMount: function(){
        var current = React.findDOMNode(this);
        hljs.highlightBlock(current);
    },
    render: function() {
        return <pre className="custom-json-body">
                    <code className="json">{JSON.stringify(this.props.json, null, 2)}</code>
               </pre>;
    }
});

PS: 一般不推荐在React中注入其他作用域的代码,推荐使用相应的React Plugins或自行扩展实现功能(尽量保持都在React的作用域)

Reference