HTML模闆
React主要是兩個庫:
react.js是核心庫
react-dom.js 是提供與 DOM 相關的功能
還有一個Browser.js 的作用是将 JSX 文法轉為 JavaScript 文法,JSX是可以将HTML直接寫在JS裡的語言,這一步通常不在浏覽器端進行,而是通過babel配合React的babel插件預先轉碼好。
$ babel src --out-dir build
不過這裡我們為了友善就直接将其引入在浏覽器端轉碼。
于是一個模闆就像是這個樣子:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>React Test</title>
</head>
<body>
<script src="../../build/react.js"></script>
<script src="../../build/react-dom.js"></script>
<script src="../../build/browser.min.js"></script>
<script type="text/babel">
//our React code
</script>
</body>
</html>
ReactDOM.render()
ReactDOM.render 是 React 的最基本方法,用于将模闆轉為 HTML 語言,并插入指定的 DOM 節點。
ReactDOM.render(
<h1>Hello, world!</h1>,
document.getElementById('render')
);
JSX 文法
HTML 語言直接寫在 JavaScript 語言之中,不加任何引号,這就是 JSX 的文法。
到 HTML 标簽(以 < 開頭),就用 HTML 規則解析;遇到代碼塊(以 { 開頭),就用 JavaScript 規則解析。
var names = ['Lion', 'and', 'Rabbit'];
ReactDOM.render(
<div>
<div>This is {
names.reduce((a,b)=>a+' '+b,'')
}!</div>
</div>,
document.getElementById('render2')
);
JSX 允許直接在模闆插入 JavaScript 變量。如果這個變量是一個數組,則會展開這個數組的所有成員:
var food = [
<span>福蘿蔔 </span>,
<span>牛肉 </span>,
];
ReactDOM.render(
<div>We like to eat:{food}</div>,
document.getElementById('render3')
);
這裡React會給出一個警告,說Each child in an array or iterator should have a unique “key” prop。
這個警告和React的Dom diff算法相關。給周遊生成的Dom節點添加key就使得React在生成虛拟Dom樹時可以記錄下你的操作,在對這部分節點進行實際DOM更新時,通過這個key可以對比原有的節點和新的節點,進行代價最小的插入和删除而不必更新整個Dom組。
這裡的key最好不是數組的下标這類的資料,而是和本Dom節點相關的Unique字元串。最次的選擇是下标,那麼我們修改一下上面的代碼:
var food = [
<span key='carrot'>福蘿蔔 </span>,
<span key='beef'>牛肉 </span>,
];
ReactDOM.render(
<div>We like to eat:{food}</div>,
document.getElementById('render3')
);
元件
React 允許将代碼封裝成元件(component),然後像插入普通 HTML 标簽一樣,在網頁中插入這個元件。
React.createClass 方法就用于生成一個元件類。
//元件類的第一個字母必須大寫,否則會報錯
var CookBook = React.createClass({
render: function() {
//元件類隻能包含一個頂層标簽,否則也會報錯
//這裡使用一個div作為頂層标簽
//class要寫為className,for寫為htmlFor,因為JS關鍵字的原因
return <div className="CookBookItem">
<h1>{this.props.name}</h1>
<div>所需時間:{this.props.time}</div>
<div>所需食材:{this.props.foods}</div>
</div>;
}
});
var foods = [
<span key='tomato'>蕃茄、</span>,
<span key='beef'>牛肉</span>,
];
ReactDOM.render(
//元件的參數通過HTML屬性傳進去,在裡面使用this.props.xxx擷取
<CookBook name="蕃茄牛腩" time="約4個小時" foods={foods}></CookBook>,
document.getElementById('cookBook1')
);
this.props.children
this.props 對象的屬性與元件的屬性一一對應,但是有一個例外,就是 this.props.children 屬性。它表示元件的所有子節點。
//元件類的第一個字母必須大寫,否則會報錯
var CookBook = React.createClass({
render: function() {
//元件類隻能包含一個頂層标簽,否則也會報錯
//這裡使用一個div作為頂層标簽
//class要寫為className,for寫為htmlFor,因為JS關鍵字的原因
return <div className="CookBookItem">
<h1>{this.props.name}</h1>
<div>所需時間:{this.props.time}</div>
<div>所需食材:{this.props.foods}</div>
<ol>
{
React.Children.map(this.props.children, function (child) {
return <li>{child}</li>;
})
}
</ol>
</div>;
}
});
var foods = [
<span key='tomato'>蕃茄、</span>,
<span key='beef'>牛肉</span>,
];
var stepsArr = [
<div key='wash'>洗菜</div>,
<div key='cutve'>切菜</div>,
<div key='cutmeet'>切肉</div>,
<div key='cook'>炖</div>,
];
ReactDOM.render(
//元件的參數通過HTML屬性傳進去,在裡面使用this.props.xxx擷取
<CookBook name="蕃茄牛腩" time="約4個小時" foods={foods}>{stepsArr}</CookBook>,
document.getElementById('cookBook1')
);
這裡需要注意, this.props.children 的值有三種可能:如果目前元件沒有子節點,它就是 undefined ;如果有一個子節點,資料類型是 object ;如果有多個子節點,資料類型就是 array 。是以,處理 this.props.children 的時候要小心。
React 提供一個工具方法 React.Children 來處理 this.props.children 。我們可以用 React.Children.map 來周遊子節點,而不用擔心 this.props.children 的資料類型是 undefined 還是 object。
PropTypes
元件的屬性可以接受任意值,字元串、對象、函數等等都可以。有時,我們需要一種機制,驗證别人使用元件時,提供的參數是否符合要求。
元件類的PropTypes屬性,就是用來驗證元件執行個體的屬性是否符合要求。
我們在剛才的例子中加入如下代碼:
//元件類的這個屬性就是用來驗證元件執行個體的屬性是否符合要求
propTypes: {
//這裡要求name屬性是必須的,且必須為字元串
name: React.PropTypes.string.isRequired,
},
再添加元件:
ReactDOM.render(
//name是必須的,是以這裡會報錯
<CookBook></CookBook>,
document.getElementById('cookBook2')
);
ReactDOM.render(
//name需要是字元串,是以這裡會報錯
<CookBook name={123} ></CookBook>,
document.getElementById('cookBook2')
);
此外,getDefaultProps 方法可以用來設定元件屬性的預設值。
//設定預設屬性
getDefaultProps : function () {
return {
time : '約1小時'
};
},
擷取真實的DOM節點
元件并不是真實的 DOM 節點,而是存在于記憶體之中的一種資料結構,叫做虛拟 DOM (virtual DOM)。隻有當它插入文檔以後,才會變成真實的 DOM 。根據 React 的設計,所有的 DOM 變動,都先在虛拟 DOM 上發生,然後再将實際發生變動的部分,反映在真實 DOM上,這種算法叫做 DOM diff ,它可以極大提高網頁的性能表現。
但是,有時需要從元件擷取真實 DOM 的節點,這時就要用到 ref 屬性。
var Login = React.createClass({
//處理點選事件
//React 元件支援很多事件,除了 Click 事件以外,還有 KeyDown 、Copy、Scroll 等
handleClick: function() {
//虛拟Dom是拿不到使用者輸入的,使用this.refs.xxx
//可以擷取到真實的Dom節點
alert(this.refs.username.value)
},
render: function() {
return (
<div>
<h1>If You Are Our Friend, Sign In Please.</h1>
<input type="text" ref="username" />
<input type="password" ref="password" />
<input type="button" value="Sign In" onClick={this.handleClick} />
</div>
);
},
//在元件加載完成時調用
componentDidMount:function(){
//焦點放在username上
this.refs.username.focus();
}
});
ReactDOM.render(
<Login />,
document.getElementById('signUp')
);
this.state
元件免不了要與使用者互動,React 的一大創新,就是将元件看成是一個狀态機,一開始有一個初始狀态,然後使用者互動,導緻狀态變化,進而觸發重新渲染 UI。
var LikeButton = React.createClass({
//設定初始狀态,是一個對象
getInitialState: function() {
return {liked: false};
},
handleClick: function(event) {
//變更狀态
//隻要狀态變更,就會調用this.render再次渲染
//至于是否真的會重新整理Dom節點,就要看React使用Dom diff算法的結果了
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>
);
}
});
ReactDOM.render(
<LikeButton />,
document.getElementById('like')
);
表單
使用者在表單填入的内容,屬于使用者跟元件的互動,是以不能用 this.props讀取,或者你通過剛才的真實Dom節點擷取,或者你使用一些事件:
var ContactUs = React.createClass({
getInitialState: function() {
return {value: '[email protected]'};
},
handleChange: function(event) {
//通過事件的target來擷取到一個真實的Dom對象就能擷取到值了
//本質上和使用ref是一樣的,都是獲得真實的Dom節點
this.setState({value: event.target.value});
},
render: function () {
var value = this.state.value;
return (
<div>
<h3>Leave Your Email</h3>
<input type="text" value={value} onChange={this.handleChange} />
<p>{value}</p>
</div>
);
}
})
ReactDOM.render(<ContactUs/>, document.getElementById('contactUs'));
元件的生命周期
元件的生命周期分成三個狀态:
- Mounting:已插入真實 DOM
- Updating:正在被重新渲染
- Unmounting:已移出真實 DOM
React 為每個狀态都提供了兩種處理函數,will 函數在進入狀态之前調用,did 函數在進入狀态之後調用,三種狀态共計五種處理函數。
- componentWillMount()
- componentDidMount()
- componentWillUpdate(object nextProps, object nextState)
- componentDidUpdate(object prevProps, object prevState)
- componentWillUnmount()
此外,React 還提供兩種特殊狀态的處理函數。
- componentWillReceiveProps(object nextProps):已加載元件收到新的參數時調用
- shouldComponentUpdate(object nextProps, object nextState):元件判斷是否重新渲染時調用
var Timer = React.createClass({
getInitialState: function () {
return {
opacity: ,
time:,
};
},
//Dom一直在變,于是這個方法總是被調用
componentDidUpdate(prevProps, prevState) {
console.log(prevState);
},
componentDidMount: function () {
this.timer = setInterval(function () {
var opacity = this.state.opacity;
var time = this.state.time;
time+=;
opacity -= ;
if (opacity < ) {
opacity = ;
}
this.setState({
opacity: opacity,
time:time,
});
}.bind(this), );
},
//每100ms重新渲染一次Dom這在以前是開銷非常大的
//但是React的先建立虛拟Dom再更改必要的實際Dom使得這個操作變得非常輕量
//注意這裡的style屬性的設定方式
//React 元件樣式是一個對象,是以第一重大括号表示這是 JavaScript 文法,第二重大括号表示樣式對象。
render: function () {
return (
<div style={{opacity: this.state.opacity}}>
You have been here for {this.state.time/}s
</div>
);
}
});
ReactDOM.render(
<Timer/>,
document.getElementById('Timer')
);
Ajax
元件的資料來源,通常是通過 Ajax 請求從伺服器擷取,可以使用 componentDidMount 方法設定 Ajax 請求,等到請求成功,再用 this.setState 方法重新渲染 UI 。
var RepoList = React.createClass({
//這個元件共有3種狀态
getInitialState: function() {
return { loading: true, error: null, data: null};
},
componentDidMount() {
//這個元件會傳進來一個promise對象
//在這個promise對象準備好之後會傳進元件并觸發這個方法
//設定完成和報錯的方法
//這裡在完成或失敗之後改變元件的狀态,重新渲染元件
this.props.promise.then(
value => this.setState({loading: false, data: value}),
error => this.setState({loading: false, error: error}));
},
//render方法根據元件的狀态重新渲染元件
render: function() {
if (this.state.loading) {
return <span>Loading...</span>;
}
else if (this.state.error !== null) {
return <span>Error: {this.state.error.message}</span>;
}
else {
var repos = this.state.data.items;
var repoList = repos.map(function (repo) {
return (
<li>
<a href={repo.html_url}>{repo.name}</a> ({repo.stargazers_count} stars) <br/> {repo.description}
</li>
);
});
return (
<main>
<h3>Most Popular JavaScript Projects in Github</h3>
<ol>{repoList}</ol>
</main>
);
}
}
});
ReactDOM.render(
<RepoList
promise={$.getJSON('https://api.github.com/search/repositories?q=javascript&sort=stars')}
/>,
document.getElementById('list')
);