React is all about modular, composable components.
-
In React, components should always represent the state of the view and not only at the point of initialization.
-
该例子的Component结构
- CommentBox
- CommentList
- Comment
- CommentForm
Component的使用
var CommentBox = React.createClass({
render: function(){
return(
<div className="commentBox">
</div>
);
}
});
ReactDOM.render(
<CommentBox />,
document.getElementById('content')
);
React.createClass()定义component的构造器,该方法接受一个JSON
对象,其中键值render定义了关键函数–该函数返回该component对应的HTML
结构.
值得注意的是,该HTML
结构的语法是一块语法糖,由JSX
实现.其相当于
// tutorial1-raw.js
var CommentBox = React.createClass({displayName: 'CommentBox',
render: function() {
return (
React.createElement('div', {className: "commentBox"},
"Hello, world! I am a CommentBox."
)
);
}
});
ReactDOM.render(
React.createElement(CommentBox, null),
document.getElementById('content')
);
ReactDom.render
接受两个参数, 一个是markup格式的component变量声明,一个是目标位置, render
函数生成的DOM将插入到该目标位置下.
You can return a tree of components that you (or someone else) built. This is what makes React composable: a key tenet of maintainable frontends.
Note that native HTML element names start with a lowercase letter, while custom React class names begin with an uppercase letter.
(注意命名规则)
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
在Component 中可以插入自定义的componet。
在元素之间传递数据
Let’s create the Comment component, which will depend on data passed in from its parent. Data passed in from a parent component is available as a ‘property’ on the child component. These ‘properties’ are accessed through
this.props
.
【从父级元素传来的数据,作为自己元素的一个属性使用】
Using props, we will be able to read the data passed to the Comment from the CommentList, and render some markup:
// tutorial4.js
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
{this.props.children}
</div>
);
}
});
We access named attributes passed to the component as keys on this.props and any nested elements as this.props.children
变量要用花括号括住。
(Q:What does it mean by “nested elements”?).
// tutorial5.js
var CommentList = React.createClass({
render: function() {
return (
<div className="commentList">
<Comment author="Pete Hunt">This is one comment</Comment>
<Comment author="Jordan Walke">This is *another* comment</Comment>
</div>
);
}
});
通过在标签中添加属性来传递参数(如该例子中的<Comment>
中的author
属性。
使用markdown语法
使用前:
...
{this.props.author}
</h2>
{this.props.children} // <===
</div>
...
修改为:
{md.render(this.props.children.toString())}
We need to convert
this.props.children
from React’s wrapped text to a raw string that remarkable will understand so we explicitly call toString().
【但这个写法是不work的。】That’s React protecting you from an XSS attack. There’s a way to get around it (but the framework warns you not to use it):
This is a special API that intentionally makes it difficult to insert raw HTML, but for remarkable we’ll take advantage of this backdoor.
需要改写为:
var Comment = React.createClass({
rawMarkup: function() {
var md = new Remarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
},
render: function() {
return (
<div className="comment">
<h2 className="commentAuthor">
{this.props.author}
</h2>
<span dangerouslySetInnerHTML={this.rawMarkup()} />
</div>
);
}
});
md.render
方法生成的是HTML
格式的文本;要使其真正能实现HTML的功能,需要按
{ __html: nameOfYourHTMLContent }
的语法书写。放在一个对象中,key值为__html。
传递来自Data Model的数据
此处以内置JSON文件作为简单示例
// CommentList的修改
var CommentList = React.createClass({
render: function() {
// 定义动态绑定
var commentNodes = this.props.data.map(function(comment) {
return (
<Comment author={comment.author} key={comment.id}>
{comment.text}
</Comment>
);
});
return (
<div className="commentList">
//this.props.data.map方法的返回结果是一串**有效的HTML文本**,直接在`render`对应的方法的返回值中作为**变量**使用即可。
{commentNodes} // <===Like this!
</div>
);
}
});
this.props.data.map
方法接受一个定义了componet及其数据的绑定格式的函数。该函数的参数就是数据的item。
// CommenBox的修改
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
// 数据首先到达父级元素CommentBox,
// 在子元素CommentList中作为属性使用
<CommentList data={this.props.data} />
<CommentForm />
</div>
);
}
});
ReactDOM.render(
<CommentBox data={data} />,
document.getElementById('content')
);
在Componet 的属性data
中绑定数据源:在ReactDOM.render
中修改 data={nameOfDataSourceVariable}
绑定服务器上的数据源
ReactDOM.render(
<CommentBox url="/api/comments" />,
document.getElementById('content')
);
使用Component 的url
属性。结合server端的代码使用。
app.get('/api/comments', function(req, res) {
fs.readFile(COMMENTS_FILE, function(err, data) {
if (err) {
console.error(err);
process.exit(1);
}
res.json(JSON.parse(data));
});
});
...
var COMMENTS_FILE = path.join(__dirname, 'comments.json');
数据的请求是最后在ReactDOM.render
函数中进行的,任何component的声明都只提供了一个空框架/模型。
使用Component的state属性实现交互
To implement interactions, we introduce mutable state to the component. this.state is private to the component and can be changed by calling this.setState(). When the state updates, the component re-renders itself.
// tutorial12.js
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
getInitialState()
: executes exactly once during the lifecycle of the component and sets up the initial state of the component. When the server fetches data, we will be changing the comment data we have.
getInitialState和render一样,都是固定语法。
实现数据的实时更新
We’re going to use jQuery to make an asynchronous request to the server we started earlier to fetch the data we need
再次修改CommentBox
,添加componentDidMount
函数:(注意componentDidMount也是固定语法)
// tutorial13.js
var CommentBox = React.createClass({
getInitialState: function() {
return {data: []};
},
componentDidMount: function() {
$.ajax({
url: this.props.url,
dataType: 'json',
cache: false,
success: function(data) {
this.setState({data: data});
}.bind(this),
error: function(xhr, status, err) {
console.error(this.props.url, status, err.toString());
}.bind(this)
});
},
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
<CommentForm />
</div>
);
}
});
componentDidMount
核心代码是this.setState()
。它做了两件事:1.从服务器取得最新数据并覆盖旧的;2.让UI自动更新。
传参语法:{data: nameOfCallBackParams}
优化代码,将AJAX请求数据的部分分离出来:
...
componentDidMount: function() {
// AJAX is wrapped up in a func.
this.loadCommentsFromServer();
/* call AJAX when the component is first loaded and every 2 seconds after that. */
setInterval(this.loadCommentsFromServer, this.props.pollInterval);
},
...
ReactDOM.render(
<CommentBox url="/api/comments" pollInterval={2000} />,
document.getElementById('content')
);
设置请求间隔:在render
函数中使用pollInterval
属性设置:pollInterval={numberOfMiliseconds}
实现用户数据输入(表单提交)
区别于传统DOM的处理方式
【传统DOM带来的问题】With the traditional DOM, input elements are rendered and the browser manages the state (its rendered value). As a result, the state of the actual DOM will differ from that of the component. This is not ideal as the state of the view will differ from that of the component.
we will be usingthis.state
to save the user’s input as it is entered.
We define an initial state with two properties author and text and set them to be empty strings.
In our elements, we set thevalue
prop to reflect the state of the component and attachonChange
handlers to them.
These<input>
elements with a value set are called controlled components.
修改CommentForm:
var CommentForm = React.createClass({
getInitialState: function() {
// 初始值赋为空
return {author: '', text: ''};
},
handleAuthorChange: function(e) {
this.setState({author: e.target.value});
},
handleTextChange: function(e) {
this.setState({text: e.target.value});
},
...
<input type="text"
placeholder="Say something..."
value={this.state.text}
onChange={this.handleTextChange}
/>
注意setState
语法: this.setState({nameOfProperty: nameOfCallbackParam.target.value});
onChange
是一个event
.
实现表单提交
Let’s make the form interactive. When the user submits the form, we should clear it,
submit a request to the server,
and refresh the list of comments.
To start, let’s listen for the form’s submit event and clear it.
修改CommentForm:
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
/*.trim()?*/
if (!text || !author) {
return;
}
// TODO: send request to the server
/*clear the form after submission*/
this.setState({author: '', text: ''});
},
...
render: function() {
return (
<form className="commentForm" onSubmit={this.handleSubmit}>
...
form
的onSubmit
监听了提交事件。
Call
preventDefault()
on the event to prevent the browser’s default action of submitting the form.
实现列表刷新:子元素传数据到父元素
It makes sense to do all of this logic in CommentBox since CommentBox owns the state that represents the list of comments.
We need to** pass data from the child component back up to its parent**. We do this in our parent’s render method by passing a new callback (handleCommentSubmit) into the child, binding it to the child’sonCommentSubmit
event. Whenever the event is triggered, the callback will be invoked.
【用回调函数的方式,实现从子元素传递数据到父元素】
修改CommentBox的render:
handleCommentSubmit: function(comment) {
// TODO: submit to the server and refresh the list
},
/* LOOK AT HERE ↓ */
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
render:
...
function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList data={this.state.data} />
/* LOOK AT HERE ↓ */
<CommentForm onCommentSubmit={this.handleCommentSubmit} />
</div>
);
}
Now that CommentBox has made the callback available to CommentForm via the onCommentSubmit prop, the CommentForm can call the callback when the user submits the form:
修改CommentForm:
...
handleSubmit: function(e) {
e.preventDefault();
var author = this.state.author.trim();
var text = this.state.text.trim();
if (!text || !author) {
return;
}
/* 父级传来的回调函数通过
this.props.nameOfCallBackMethod来调用!
LOOK AT HERE ↓ */
this.props.onCommentSubmit({author: author, text: text});
this.setState({author: '', text: ''});
},
...
得到CommentForm的数据之后,CommentBox就可以将数据提交到服务器(url)并更新列表了:
// CommentBox
handleCommentSubmit: function(comment) {
$.ajax({
url: this.props.url,
dataType: 'json',
type: 'POST',
// 发送数据的ajax请求
data: comment,
success: function(data) {
this.setState({data: data});
}...);
},
将回调结果’comment’写入键值’data’。
优化列表刷新:本地刷新
In CommentBox
handleCommentSubmit: function(comment) {
var comments = this.state.data;
// comments是当前的数据大数组
comment.id = Date.now();
// 将新的comment添加(concat)到comments中
var newComments = comments.concat([comment]);
// 本地手动更新
this.setState({data: newComments});
$.ajax({
...
总结
- 学会了
component
的基本使用; - 数据和component的绑定;
- 数据源的绑定(本地/服务器)。
- JSX作为语法糖的使用。
- 运用元素的
prop
(properties)进行数据传递,以及在此之上利用回调函数进行从子元素到父元素的数据传递。 - 使用markdown渲染文本。
Q:.state和.props在数据传递上的区别是什么?
So far, based on its props, each component has rendered itself once. props are immutable: they are passed from the parent and are “owned” by the parent.
【.props】是不会变动的,在生成时初始化并不再更改。
this.state
is private to the component and can be changed by callingthis.setState()
. When the state updates, the component re-renders itself.
动手
最近刚好在学SQL,可以用react.js做一个数据可视化的工具。
拓展
Forms Article:进阶使用Controlled Components
automatically binds :事件绑定
thinking in react