參考:《React 入門實例教程--阮一峰》、《React 學習教程--眾成翻譯》璃俗。React 框架入門學習摘錄姻报。
搭建開發(fā)環(huán)境
React 的學習曲線相當陡峭站欺,其實是入門階段里面也是各種坑蜻懦。
本篇開頭從搭建學習用的開發(fā)環(huán)境入手甜癞。React 很多時候使用 JSX 編寫。JSX是一門混合編寫 JS 和 HTML 的 JS 語法糖宛乃。所以悠咱,首先我們必須編譯 JSX 為瀏覽器能夠識別的 JS。在學習之初烤惊,我是在頁面中直接引用 React 的關鍵庫和編譯用的 babel 庫文件乔煞。
<script src="./libs/react.min.js"></script>
<script src="./libs/react-dom.min.js"></script>
<script src="./libs/browser.min.js"></script>
前面兩個文件可以在官網中下載吁朦,也可以使用 npm 下載柒室。建議使用 npm 下載,以方便之后結合 webpack 逗宜、gulp 工具來處理 JSX雄右。
cnpm install --save-dev react react-dom
注意空骚,最后的一個文件是 babel 提供的在瀏覽器環(huán)境使用的 babel 工具。babel 既能將 ES6 轉換為 ES5,同時也支持編譯 JSX擂仍。這個文件我是從 browser.min.js下載再拷貝到本地(實踐證明囤屹,從npm下載的 babel-core 里不包含這個文件)。
最后貼出這里的 HTML 模板:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>React Tutorial</title>
<script src="./libs/react.min.js"></script>
<script src="./libs/react-dom.min.js"></script>
<script src="./libs/browser.min.js"></script>
</head>
<body>
<div id="content"></div>
<script type="text/babel">
// JSX
</script>
</body>
</html>
體驗 JSX 語法
HTMl 語言直接寫在 JavaScript 中逢渔,不加任何引號肋坚,就是 JSX 語法。JSX 的另一個基本語法規(guī)則:遇到 HTML 標簽的(以 <></> 標簽對包裹)肃廓,就用 HTML 規(guī)則解析智厌;遇到代碼塊(以 {} 包裹),就用 JavaScript 解析盲赊。如以下代碼(如無特殊說明铣鹏,之后的代碼塊都是寫在<scirpt type="text/bable"></script>
中的):
var names = ['Alice', 'Emily', 'Kate'];
ReactDOM.render(
<div>
{
names.map(function (name) {
return <div>Hello, {name}!</div>
})
}
</div>,
document.getElementById('example')
))
簡單搭建學習用的 gulp 工具箱
現(xiàn)在只使用 gulp 工具和 browser-sync 來實現(xiàn)文件的熱加載。
cnpm install --save-dev gulp browser-sync
安裝完成后哀蘑,在項目目錄下面添加 gulpfile.js 文件诚卸。里面的代碼如下:
var gulp = require('gulp');
var browserSync = require('browser-sync').create();
gulp.task('server', function(){
browserSync.init({
server: {
baseDir: './src'
}
});
gulp.watch('./src/*.html', function(file){
console.log(file.path + 'is changed __');
browserSync.reload(); // 一旦監(jiān)視文件修改,就刷新瀏覽器
})
});
組件
React 的組件是網頁或 spa 里完成最小功能的一部分代碼绘迁,這部分代碼會包含該組件的基本功能合溺,視覺樣式和交互能力。使用 React 開發(fā)缀台,首先就需要將產品切割成一個又一個組件辫愉,然后,將它們拼裝成產品将硝。下面的代碼定義了一個 HelloMessage 組件恭朗。
var HelloMessage = React.creatClass({
render: function(){
return <h1>Hello,{this.props.name}</h1>
}
});
接下來看怎么使用這個組件:
ReactDOM.render(
<HelloMessage name="John" />, document.getElementById("content")
);
然后在 power shell(window 10 ) 運行 gulp server
。在打開的網頁中應該顯示 “Hello,John”依疼。
注意痰腮,組件類的命名使用雙駝峰方式,并且每一個組件類只能有一個頂層的標簽律罢。
React 創(chuàng)建組件的三種方法
stateless function VS creatClass VS ES6 Class
出于不同的原因膀值,React 先后出現(xiàn)了三種定義 react 組件的方式。分別是:
- 函數(shù)式定義的無狀態(tài)組件(stateless function)
- es5 原生的 React.creatClass 定義的組件
- es6 形式的 extends React.Componet 定義的組件
1. 無狀態(tài)函數(shù)式組件
無狀態(tài)函數(shù)式組件式為了創(chuàng)建純展示組件误辑,這類組件只負責根據(jù)傳入的 props 來展示沧踏,不涉及到要 state 的操作。無狀態(tài)函數(shù)式組件表現(xiàn)為一個只帶有 render 方法的組件類巾钉,通過函數(shù)形式或 ES6 arrow function 的形式創(chuàng)建翘狱。代碼如下:
function HelloComponent(props, context){
return <div>Hello, {props.name}</div>
}
ReactDOM.render(<HelloComponent name="John") />,
document.getElementById('content'));
細節(jié)待續(xù)……
2.React.creatClass 定義的組件
React.createClass
是 react 剛開始推薦的創(chuàng)建組件的方式,也是 es5 原生的 javascript 實現(xiàn) React 組件砰苍,代碼如下:
var InputControl = React.createClass({
propTypes: { // 定義傳入 props 中的屬性各種類型
initialValue: React.PropTypes.string
},
defaultPorps: { // 組件默認的 props 對象
initialValue: ''
},
// 設置 initial state
getInitialState: function() { // 組件相關狀態(tài)對象
return {
text: this.props.initialValue || 'placeholder'
};
},
handleChange: function () {
this.setState({
text: event.target.value
});
},
render: function() {
return (
<div>
Type something:
<input onChange={this.handleChange} value= {this.state.text} />
<p>{this.state.text}</p>
</div>
);
}
});
ReactDOM.render(<InputControl />, document.getElementById('content'));
該方式創(chuàng)建的組件會被實例化潦匈,可以訪問組件的生命周期方法阱高。
3. ES6 形式的創(chuàng)建組件
代碼如下:
class InputControl extends React.Component {
constructor(props) {
super(props);
// 設置 intitial state
this.state = {
text: props.initialValue || 'placeholder'
};
// ES6 類中函數(shù)必須手動綁定
this.handleChange = this.handleChange.bind(this);
}
handleChange(event) {
this.setState({
text: event.target.value
});
}
render() {
return (
<div>Type something:
<input onChange={this.handleChange} value={this.state.text}/>
<p>{this.state.text}</p>
</div>
);
}
}
InputControl.propTypes = {
initialValue: React.PropTypes.string
};
InputControl.defaultProps = {
initialValue: ''
};
//這里使用組件的時候,不需要 new 實例化茬缩。
ReactDOM.render(<InputControl/>, document.getElementById('content'));
這種方式和之前 ES5 的差別比較大赤惊,也是目前 React 比較推薦的寫法。delay……
組件的生命周期
一個組件就是一個狀態(tài)機:對于特定的輸入凰锡,它總會返回一致的輸出未舟。React 為組件提供了生命周期鉤子函數(shù)去響應不同的時期——創(chuàng)建、存在期及銷毀掂为。
- Mounting: 已插入真實的 DOM
- Updating: 正在重新渲染
- Unmounting: 移除真實的 DOM
Mounting 和 Updating 時期都有兩個方法:componentWillMount处面,componentDidMount 和 componentWillUpdate,componentDidUpdate菩掏。
同時魂角,Mounting 時期還分為兩種情況,一種是組件第一次被 Mounting智绸,一種是后續(xù)被 Mounting野揪。它們都有 getInitialState 方法,但是初次 mounting 還有一個 getDefaultProps 方法瞧栗。
Updating 時期 還有兩個特殊方法 componentWillReceiveProps斯稳,shouldComponentUpdate。 Unmounting 只有一個方法:componentWillUnmount迹恐。
同時可以參考以下圖片來理解組件的生命周期:
接下來在代碼中看看這些方法的調用挣惰。
var LogStatus = React.createClass({
getDefaultProps: function() {
console.log('組件開始實例化……設置默認 props');
},
getInitialState: function() {
console.log('組件開始實例化……設置默認 state');
return {
num: 0
};
},
componentWillMount: function(){
console.log('組件即將實例化……');
},
componentWillReceiveProps: function() {
console.log('組件即將初始化……并更新 state');
},
componentWillUpdate: function() {
console.log('組件即將更新……');
},
onTest: function(ev) {
this.setState(function(state, props){
var i = state.num + 1;
return {num: i};
})
},
render: function(){
var num = this.state.num;
return (<h1 onClick={this.onTest}>this.props.text
<span>{num}</span></h1>);
},
componentDidMount: function() {
console.log('組件實例化完成……');
},
componentDidUpdate: function() {
console.log('組件更新完成……');
}
});
ReactDOM.render(
<LogStatus text="組件第一次實例化"></LogStatus>,
document.getElementById('content')
);
ReactDOM.render(
<LogStatus text="組件第二次實例化"></LogStatus>,
document.getElementById('content2')
)
代碼里基本演示了各個階段調用的方法。但這是 ES5 是的生命周期函數(shù)殴边,然而憎茂,現(xiàn)在,React 更推薦的 ES6 寫法锤岸,對生命周期函數(shù)也進行了調整竖幔。
參考代碼如下:
class LifeCycle extends React.Component {
constructor (props) {
super(props);
this.state = {
str : 's'
};
console.log("Initial render");
console.log('constructor');
}
componentWillMount() {
console.log("component will mount");
}
componentWillReceiveProps(nextProps){
console.log("component will receive props");
}
shouldComponentUpdate() {
console.log("should component update");
return true;
}
componentDidUpdate() {
console.log("component did updates");
}
componentWillUnmount () {
console.log('component will unmount');
}
setTheState() {
let s = "hello";
if(this.state.str === 's'){
s = "HELLO";
}
this.setState({
str: s
})
}
forceItUpdate() {
this.forceUpdate();
}
render() {
console.log("render");
return (
<div>
<span>{"Props:"}<h2>{parseInt(this.props.num)}</h2>
</span>
<br></br>
<span>{"state:"}<h2>{this.state.str}</h2>
</span>
</div>
);
}
}
class Container extends React.Component {
constructor(props) {
super(props);
this.state = {
num: Math.random() * 100
};
}
propsChange() {
this.setState({
num: Math.random() * 100
})
}
setLifeCycleState() {
this.refs.rLifeCycle.setTheState();
}
forceLifeCycleUpdate() {
this.refs.rLifeCycle.forceItUpdate();
}
parentForceUpdate() {
this.forceUpdate();
}
render() {
return (
<div>
<a href="javascript:;" className="weui_btn weui_btn_primary"
onClick={this.propsChange.bind(this)}>propsChange
</a>
<br/>
<a href="javascript:;" className="weui_btn weui_btn_primary"
onClick={this.setLifeCycleState.bind(this)}>
setState
</a>
<br/>
<a href="javascript:;" className="weui_btn weui_btn_primary"
onClick={this.forceLifeCycleUpdate.bind(this)}>
forceUpdate
</a>
<br/>
<a href="javascript:;" className="weui_btn weui_btn_primary"
onClick={this.parentForceUpdate.bind(this)}>
parentForceUpdateWithoutChange
</a>
<br/>
<LifeCycle ref="rLifeCycle" num={this.state.num}></LifeCycle>
</div>
);
}
}
ReactDOM.render(
<Container></Container>, document.getElementById('content')
)
沒有找到銷毀一個 React component 的方法,所以是偷,componentWillUnmount() 沒有使用拳氢。
數(shù)據(jù)流
在 React,數(shù)據(jù)的流向是單向的,從父節(jié)點傳遞到子節(jié)點蛋铆。在之前的代碼已反復使用了數(shù)據(jù)流里相關的兩個參數(shù) props, state馋评。
1.Props
Props 就是 properties 的縮寫。它可以把任意類型的數(shù)據(jù)傳遞給組件刺啦。
可以在掛載組件的時候設置它的 props:
var MyTitle = React.createClass({
render: function(){
return (
<h1>{this.props.title}</h1>
);
}
});
ReactDOM.render(
<MyTitle title="hello留特,world"></MyTitle>,
document.getElementById('content')
)
上面代碼中,在掛載時傳入 title="hello,world"
的參數(shù),這里的參數(shù)都會添加到 props 中磕秤。
另外可以通過調用組件實例的 serProps 方法(很少需要這樣做)來設置其 props:
var listSurveys = React.render(
<ListSurveys />, document.querySlector('body')
);
listSurveys.serProps({surveys: surveys});
你只能在子組件或者在組件樹外調用 setProps。千萬別使用 this.setProps 或者直接修改 this.props捧韵。
- PropTypes
通過在組件中定義一個配置對象市咆,React提供了一中驗證 props 的方式:
var SurveyTableRow = React.createClass({
propTypes: {
survey: React.PropTypes.shape({
id: React.PropTypes.number.isRequired
}).isRequired,
onClick: React.PropTypes.func
},
……
});
組件初始化的時候,如果傳遞的屬性和 propTypes 不匹配再来,則會打印一個 console.warn 日志蒙兰。
如果是可選的配置,則可以去掉 .isRequired芒篷。
注意搜变,在應用中使用的 propTypes 并不是強制的,但這提供了一種描述組件 API 的方式针炉。
但在 ES6 的寫法中挠他,是單獨將 propTypes 綁定在組件類中了,寫法如下:
class CustomButton extends React.Component {
// ...
}
CustomButton.propTypes = {
name: React.PropTypes.string
};
- getDefaultProps
如果要添加屬性的默認值篡帕,可以使用 getDefaultProps 函數(shù)殖侵。不過,這應該只針對那些非必要屬性镰烧。
var SurveyTable = React.createClass({
getDefaultProps: function() {
return {
surveys: []
};
}
});
注意拢军,getDefaultProps 并不是在組件實例化的時候被調用的,而是在 React.createClass 調用的時候調用的怔鳖,返回值會被緩存起來茉唉,所以,不能在 這個函數(shù)里調用任何特定的實例數(shù)據(jù)结执。下面再看看 ES6 的實現(xiàn):
class CustomButton extends React.Component {
// ...
}
CustomButton.defaultProps = {
color: 'blue'
};
2.State
每個 React 組件都有自己的 state,但是 state 只存在于組件的內部度陆。state 是用來確定和修改組件的狀態(tài)的。一個組件與用戶交互的過程中献幔,會根據(jù)用戶的輸入不斷更新狀態(tài)坚芜,實際就是依靠 state 來更新的。具體實現(xiàn)看下面的代碼:
var CountryDropdown = React.createClass({
getInitialState : function(){
return {
showOptions: false
};
},
render: function() {
var options;
if(this.state.showOptions){
options = (
<ul className='options'>
<li>United States of America</li>
<li>New Zealand</li>
<li>Denmark</li>
</ul>
);
}
return (
<div className="dropdown" onClick={this.handleClick}>
<label>Choose a country </lable>.{options}
</div>
);
},
handleClick: function()}{
this.setState({
showOptions: true
});
}
});
注意斜姥,不能直接使用 this.state 來修改 state, 而是要使用 this.setState 來修改鸿竖。
事件處理
React 事件本質上和 JavaScript 事件一樣。所有事件在命名上也和原生 JavaScript 規(guī)范一致铸敏,并且會在相同的請將下被觸發(fā)缚忧。但是,React 綁定事件處理器的語法和 直接在 HTML 綁定事件的語法類似杈笔。代碼如下:
<button className="btn btn-save"
onClick={this.handleSaveClicked}> Save </button>
用戶點擊按鈕時闪水,handleSaveClicked()會被調用。事實上蒙具,這種寫法雖然和 HTML 內聯(lián)事件寫法類似球榆,其實在底層實現(xiàn)上并沒有使用 HTML 的 onClick 屬性朽肥。React 只是用這個寫法來綁定事件處理器,其內部則按需高效地維護著事件處理器持钉。