标签:react web dom frameworks
React并不是一个完整的MVC或者MVVM框架,它与Angular也是负责不同的方面,它最大的功能是提供一个高效的视图层。React提供了一些新颖的概念、库和编程原则让你能够同时在服务端和客户端编写快速、紧凑、漂亮的代码来构建你的web应用。如果你使用React,那么可能会涉及到一些常用的概念或技术,包括:
同构React(isomorphic React)
在具体的React实践中,考虑到纯粹的UI或者UX设计人员,他们可能只会将CSS与HTML进行组合,换言之,大量的赋值还是会放置在HTML而非JSX中,建议还是可以运用jQuery+React或者Angular+React的方式。
如我们所知,在浏览器渲染网页的过程中,加载到HTML文档后,会将文档解析并构建DOM树,然后将其与解析CSS生成的CSSOM树一起结合产生爱的结晶——RenderObject树,然后将RenderObject树渲染成页面(当然中间可能会有一些优化,比如RenderLayer树)。这些过程都存在与渲染引擎之中,渲染引擎在浏览器中是于JavaScript引擎(JavaScriptCore也好V8也好)分离开的,但为了方便JS操作DOM结构,渲染引擎会暴露一些接口供JavaScript调用。由于这两块相互分离,通信是需要付出代价的,因此JavaScript调用DOM提供的接口性能不咋地。各种性能优化的最佳实践也都在尽可能的减少DOM操作次数。
而虚拟DOM干了什么?它直接用JavaScript实现了DOM树(大致上)。组件的HTML结构并不会直接生成DOM,而是映射生成虚拟的JavaScript DOM结构,React又通过在这个虚拟DOM上实现了一个 diff 算法找出最小变更,再把这些变更写入实际的DOM中。这个虚拟DOM以JS结构的形式存在,计算性能会比较好,而且由于减少了实际DOM操作次数,性能会有较大提升。
React渲染出来的HTML标记都包含了`data-reactid`属性,这有助于React中追踪DOM节点。
在React中,应用利用State与Props对象实现单向数据流的传递。换言之,在一个多组件的架构中,某个父类组件只会负责响应自身的State,并且通过Props在链中传递给自己的子元素。
/** @jsx React.DOM */
var FilteredList = React.createClass({
filterList: function(event){
var updatedList = this.state.initialItems;
updatedList = updatedList.filter(function(item){
return item.toLowerCase().search(
event.target.value.toLowerCase()) !== -1;
});
this.setState({items: updatedList});
},
getInitialState: function(){
return {
initialItems: [
"Apples",
"Broccoli",
"Chicken",
"Duck",
"Eggs",
"Fish",
"Granola",
"Hash Browns"
],
items: []
}
},
componentWillMount: function(){
this.setState({items: this.state.initialItems})
},
render: function(){
return (
<div className="filter-list">
<input type="text" placeholder="Search" onChange={this.filterList}/>
<List items={this.state.items}/>
</div>
);
}
});
var List = React.createClass({
render: function(){
return (
<ul>
{
this.props.items.map(function(item) {
return <li key={item}>{item}</li>
})
}
</ul>
)
}
});
React.render(<FilteredList/>, document.getElementById(‘mount-point‘));
基本的React的页面形式如下所示:
<!DOCTYPE html>
<html>
<head>
<script src="../build/react.js"></script>
<script src="../build/JSXTransformer.js"></script>
</head>
<body>
<div id="example"></div>
<script type="text/jsx">
// ** Our code goes here! **
</script>
</body>
</html>
React独创了一种JS、CSS和HTML混写的JSX格式,可以通过在页面中引入JSXTransformer这个文件进行客户端的编译,不过还是推荐在网页端编译,笔者习惯使用Babel这个平台。
var HelloMessage = React.createClass({
render: function() {
return <div>Hello {this.props.name}</div>;
}
});
React.render(
<HelloMessage name="John" />,
document.getElementById(‘container‘)
);
React.render 是 React 的最基本方法,用于将模板转为 HTML 语言,并插入指定的 DOM 节点。要注意的是,React的渲染函数并不是简单地把HTML元素复制到页面上,而是维护了一张Virtual Dom映射表。
class ExampleComponent extends React.Component {
constructor() {
super();
this. _handleClick = this. _handleClick.bind(this);
this.state = Store.getState();
}
// ...
}
完整的React开发环境应该包括了JSX/ES6的解析以及模块化管理,笔者在这里是选用了WebPack与Babel。Webpack是一个强大的包管理以及编译工具,
参考资料
Webpack是一个非常强大依赖管理与打包工具,其基本的配置方式可以如下:
var path = require(‘path‘);
var node_modules = path.resolve(__dirname, ‘node_modules‘);
var pathToReact = path.resolve(node_modules, ‘react/dist/react.min.js‘);
config = {
entry: [‘webpack/hot/dev-server‘, path.resolve(__dirname, ‘app/main.js‘)],
resolve: {
alias: {
‘react‘: pathToReact
}
},
output: {
path: path.resolve(__dirname, ‘build‘),
filename: ‘bundle.js‘,
},
module: {
loaders: [{
test: /\.jsx?$/,
loader: ‘babel‘
}],
noParse: [pathToReact]
}
};
module.exports = config;
一个典型的项目结构你可以参考这个仓库。
config/
app.js
webpack.js (js config over json -> flexible)
src/
app/ (the React app: runs on server and client too)
components/
__tests__ (Jest test folder)
AppRoot.jsx
Cart.jsx
Item.jsx
index.js (just to export app)
app.js
client/ (only browser: attach app to DOM)
styles/
scripts/
client.js
index.html
server/
index.js
server.js
.gitignore
.jshintrc
package.json
README.md
Angular与React是笔者喜欢的两个框架,二者可以相辅相成。可以查看笔者的这个库。
HTML 语言直接写在 JavaScript 语言之中,不加任何引号,这就是 JSX 的语法,它允许 HTML 与 JavaScript 的混写。
var names = [‘Alice‘, ‘Emily‘, ‘Kate‘];
React.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById(‘example‘)
);
上面代码体现了 JSX 的基本语法规则:遇到 HTML 标签(以 < 开头),就用 HTML 规则解析;遇到代码块(以 { 开头),就用 JavaScript 规则解析。JSX 允许直接在模板插入 JavaScript 变量。如果这个变量是一个数组,则会展开这个数组的所有成员:
var arr = [
<h1>Hello world!</h1>,
<h2>React is awesome</h2>,
];
React.render(
<div>{arr}</div>,
document.getElementById(‘example‘)
);
JSX编译器的核心是将基于XML的语言编译成JS代码,主要是依赖于React.createElment函数。
var Nav;
// Input (JSX):
var app = <Nav color="blue" />;
// Output (JS):
var app = React.createElement(Nav, {color:"blue"});
var Nav, Profile;
// Input (JSX):
var app = <Nav color="blue"><Profile>click</Profile></Nav>;
// Output (JS):
var app = React.createElement(
Nav,
{color:"blue"},
React.createElement(Profile, null, "click")
);
如果需要在HTML中混入JavaScript变量值,需要利用{}来代替”“。
// Input (JSX):
var person = <Person name={window.isLoggedIn ? window.name : ‘‘} />;
// Output (JS):
var person = React.createElement(
Person,
{name: window.isLoggedIn ? window.name : ‘‘}
);
// These two are equivalent in JSX for disabling a button
<input type="button" disabled />;
<input type="button" disabled={true} />;
// And these two are equivalent in JSX for not disabling a button
<input type="button" />;
<input type="button" disabled={false} />;
// Input (JSX):
var content = <Container>{window.isLoggedIn ? <Nav /> : <Login />}</Container>;
// Output (JS):
var content = React.createElement(
Container,
null,
window.isLoggedIn ? React.createElement(Nav) : React.createElement(Login)
);
var content = (
<Nav>
{/* child comment, put {} around */}
<Person
/* multi
line
comment */
name={window.isLoggedIn ? window.name : ‘‘} // end of line comment
/>
</Nav>
);
React提供了和以往不一样的方式来看待视图,它以组件开发为基础。组件是React的核心概念,React 允许将代码封装成组件(component),然后像插入普通 HTML 标签一样,在网页中插入这个组件。React.createClass 方法就用于生成一个组件类。对React应用而言,你需要分割你的页面,使其成为一个个的组件。也就是说,你的应用是由这些组件组合而成的。你可以通过分割组件的方式去开发复杂的页面或某个功能区块,并且组件是可以被复用的。这个过程大概类似于用乐高积木去瓶装不同的物体。我们称这种编程方式称为**组件驱动开发**。
组件的生命周期分成三个状态:
Mounting:已插入真实 DOM,即Initial Render
Updating:正在被重新渲染,即Props与State改变
Unmounting:已移出真实 DOM,即Component Unmount
React 为每个状态都提供了两种处理函数,will 函数在进入状态之前调用,did 函数在进入状态之后调用,三种状态共计五种处理函数。
componentWillMount()
componentDidMount()
componentWillUpdate(object nextProps, object nextState)
componentDidUpdate(object prevProps, object prevState)
componentWillUnmount()
此外,React 还提供两种特殊状态的处理函数。
这里可以看出,Props比State的改变会有多出一个shouldComponentUpdate的回调方法。
总结而言,一个完整的React Component的写法应该如下:
/**
* @jsx React.DOM
*/
var React = require(‘react‘),
MyReactComponent = React.createClass({
// The object returned by this method sets the initial value of this.state
getInitialState: function(){
return {};
},
// The object returned by this method sets the initial value of this.props
// If a complex object is returned, it is shared among all component instances
getDefaultProps: function(){
return {};
},
// Returns the jsx markup for a component
// Inspects this.state and this.props create the markup
// Should never update this.state or this.props
render: function(){
return (<div></div>);
},
// An array of objects each of which can augment the lifecycle methods
mixins: [],
// Functions that can be invoked on the component without creating instances
statics: {
aStaticFunction: function(){}
},
// -- Lifecycle Methods --
// Invoked once before first render
componentWillMount: function(){
// Calling setState here does not cause a re-render
},
// Invoked once after the first render
componentDidMount: function(){
// You now have access to this.getDOMNode()
},
// Invoked whenever there is a prop change
// Called BEFORE render
componentWillReceiveProps: function(nextProps){
// Not called for the initial render
// Previous props can be accessed by this.props
// Calling setState here does not trigger an an additional re-render
},
// Determines if the render method should run in the subsequent step
// Called BEFORE a render
// Not called for the initial render
shouldComponentUpdate: function(nextProps, nextState){
// If you want the render method to execute in the next step
// return true, else return false
return true;
},
// Called IMMEDIATELY BEFORE a render
componentWillUpdate: function(nextProps, nextState){
// You cannot use this.setState() in this method
},
// Called IMMEDIATELY AFTER a render
componentDidUpdate: function(prevProps, prevState){
},
// Called IMMEDIATELY before a component is unmounted
componentWillUnmount: function(){
}
});
module.exports = MyReactComponent;
设置默认的Props.
this.props 对象的属性与组件的属性一一对应,但是有一个例外,就是 this.props.children 属性。它表示组件的所有子节点。
var NotesList = React.createClass({
render: function() {
return (
<ol>
{
this.props.children.map(function (child) {
return <li>{child}</li>
})
}
</ol>
);
}
});
React.render(
<NotesList>
<span>hello</span>
<span>world</span>
</NotesList>,
document.body
);
其效果图如下所示:
React.createClass({
propTypes: {
// You can declare that a prop is a specific JS primitive. By default, these
// are all optional.
optionalArray: React.PropTypes.array,
optionalBool: React.PropTypes.bool,
optionalFunc: React.PropTypes.func,
optionalNumber: React.PropTypes.number,
optionalObject: React.PropTypes.object,
optionalString: React.PropTypes.string,
// Anything that can be rendered: numbers, strings, elements or an array
// containing these types.
optionalNode: React.PropTypes.node,
// A React element.
optionalElement: React.PropTypes.element,
// You can also declare that a prop is an instance of a class. This uses
// JS‘s instanceof operator.
optionalMessage: React.PropTypes.instanceOf(Message),
// You can ensure that your prop is limited to specific values by treating
// it as an enum.
optionalEnum: React.PropTypes.oneOf([‘News‘, ‘Photos‘]),
// An object that could be one of many types
optionalUnion: React.PropTypes.oneOfType([
React.PropTypes.string,
React.PropTypes.number,
React.PropTypes.instanceOf(Message)
]),
// An array of a certain type
optionalArrayOf: React.PropTypes.arrayOf(React.PropTypes.number),
// An object with property values of a certain type
optionalObjectOf: React.PropTypes.objectOf(React.PropTypes.number),
// An object taking on a particular shape
optionalObjectWithShape: React.PropTypes.shape({
color: React.PropTypes.string,
fontSize: React.PropTypes.number
}),
// You can chain any of the above with `isRequired` to make sure a warning
// is shown if the prop isn‘t provided.
requiredFunc: React.PropTypes.func.isRequired,
// A value of any data type
requiredAny: React.PropTypes.any.isRequired,
// You can also specify a custom validator. It should return an Error
// object if the validation fails. Don‘t `console.warn` or throw, as this
// won‘t work inside `oneOfType`.
customProp: function(props, propName, componentName) {
if (!/matchme/.test(props[propName])) {
return new Error(‘Validation failed!‘);
}
}
},
/* ... */
});
React不提倡数据的双向绑定,而在用户行为下面产生的数据更新,React建议还是通过事件机制来处理。譬如下述例子中,输入框文本内容的改变,还是通过onChange事件,然后出发状态机的变化。
var LikeButton = React.createClass({
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
this.setState({liked: !this.state.liked});
},
render: function() {
var text = this.state.liked ? ‘like‘ : ‘haven\‘t liked‘;
return (
<p onClick={this.handleClick}>
You {text} this. Click to toggle.
</p>
);
}
});
React.render(
<LikeButton />,
document.getElementById(‘example‘)
);
参考资料
组件的主要职责是将原始数据转化为HTML中的富文本格式,而Props与State协作完成这件事,换言之,Props与State的并集即是全部的原始数据。Props与State之间也是有很多交集的,譬如:
不过Props顾名思义,更多的是作为Component的配置项存在。Props往往是由父元素指定并且传递给自己的子元素,不过自身往往不会去改变Props的值。另一方面,State在组件被挂载时才会被赋予一个默认值,而常常在与用户的交互中发生更改。往往一个组件独立地维护它的整个状态机,可以认为State是一个私有属性。他们的对比如下:
描述 | Props | State |
---|---|---|
是否可以从父元素获取初始值 | Yes | Yes |
是否可以被父元素改变 | Yes | No |
是否可以设置默认值 | Yes | Yes |
是否可以在组件内改变 | No | Yes |
是否可以设置为子元素的初始值 | Yes | Yes |
是否可以在子元素中改变 | Yes | No |
组件并不是真实的 DOM 节点,而是存在于内存之中的一种数据结构,叫做虚拟 DOM (virtual DOM)。只有当它插入文档以后,才会变成真实的 DOM 。根据 React 的设计,所有的 DOM 变动,都先在虚拟 DOM上发生,然后再将实际发生变动的部分,反映在真实 DOM上,这种算法叫做 DOM diff ,它可以极大提高网页的性能表现。但是,有时需要从组件获取真实 DOM 的节点,这时就要用到 React.findDOMNode 方法。
var MyComponent = React.createClass({
handleClick: function() {
React.findDOMNode(this.refs.myTextInput).focus();
},
render: function() {
return (
<div>
<input type="text" ref="myTextInput" />
<input type="button" value="Focus the text input" onClick={this.handleClick} />
</div>
);
}
});
React.render(
<MyComponent />,
document.getElementById(‘example‘)
);
需要注意的是,由于 React.findDOMNode 方法获取的是真实 DOM ,所以必须等到虚拟 DOM 插入文档以后,才能使用这个方法,否则会返回 null 。上面代码中,通过为组件指定 Click 事件的回调函数,确保了只有等到真实 DOM 发生 Click 事件之后,才会调用 React.findDOMNode 方法。
设置State的初始状态。
var MyComponent = React.createClass({
getInitialState: function(){
return {
count: 5
}
},
render: function(){
return (
<h1>{this.state.count}</h1>
)
}
});
参考资料
在React中,如果要使用行内元素,不可以直接使用style="”这种方式,可以有:
import React from ‘react‘;
var style = {
backgroundColor: ‘#EEE‘
};
export default React.createClass({
render: function () {
return (
<div style={style}>
//或者<div style={{backgroundColor: ‘#EEE‘}}>
<h1>Hello world</h1>
</div>
)
}
});
你可以根据这个策略为每个组件创建 CSS 文件,可以让组件名和 CSS 中的 class 使用一个命名空间,来避免一个组件中的一些 class 干扰到另外一些组件的 class。
app/components/MyComponent.css
.MyComponent-wrapper {
background-color: #EEE;
}
app/components/MyComponent.jsx
import ‘./MyComponent.css‘;
import React from ‘react‘;
export default React.createClass({
render: function () {
return (
<div className="MyComponent-wrapper">
<h1>Hello world</h1>
</div>
)
}
});
React对于事件的支持非常完善,可以查看这里。
If you’d like to use React on a touch device such as a phone or tablet, simply call React.initializeTouchEvents(true);
to enable touch event handling.
譬如在某个子组件中,提供了某个方法:
var ButtonComponent = React.createClass({
getDragonKillingSword: function(){
//送宝刀
},
render: function(){
return (<button onClick={this.getDragonKillingSword}>屠龙宝刀,点击就送</button>);
}
});
如果在父组件中想手动调用该方法,则可以利用ref方式:
var ImDaddyComponent = React.createClass({
render: function(){
return (
<div>
//其他组件
<ButtonComponent />
//其他组件
</div>
);
}
});
在父组件的功能方程中:
this.refs.getSwordButton.getDragonKillingSword();
反之,如果需要在子组件中调用父组件的方法,则可以直接将父组件的方法作为Props参数传入到子组件中:
<ButtonComponent clickCallback={this.getSwordButtonClickCallback}/>
组件的数据来源,通常是通过 Ajax 请求从服务器获取,可以使用 componentDidMount 方法设置 Ajax 请求,等到请求成功,再用 this.setState 方法重新渲染 UI。
var UserGist = React.createClass({
getInitialState: function() {
return {
username: ‘‘,
lastGistUrl: ‘‘
};
},
componentDidMount: function() {
$.get(this.props.source, function(result) {
var lastGist = result[0];
if (this.isMounted()) {
this.setState({
username: lastGist.owner.login,
lastGistUrl: lastGist.html_url
});
}
}.bind(this));
},
render: function() {
return (
<div>
{this.state.username}‘s last gist is
<a href={this.state.lastGistUrl}>here</a>.
</div>
);
}
});
React.render(
<UserGist source="https://api.github.com/users/octocat/gists" />,
document.body
);
不过笔者习惯还是将整个获取数据,处理数据的业务逻辑放在Angular中进行。
对于React组件的测试,这里推荐使用[Jest](https://facebook.github.io/jest/),Jest也是由Facebook提供的测试框架,并且有很多强大的特性,但这里并会详细的介绍它们。关于Jest,我推荐你阅读和尝试来自Facebook的[Tutorial](https://facebook.github.io/jest/docs/tutorial-react.html#content)。对于ES6代码的测试,你可以参考 [React ES6 Testing](https://github.com/facebook/jest/tree/master/examples/react-es6)。
版权声明:本文为博主原创文章,未经博主允许不得转载。
标签:react web dom frameworks
原文地址:http://blog.csdn.net/wxyyxc1992/article/details/47791709