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