
這是幾個(gè)入門學(xué)習(xí) React 的小 Demo,幫助自己學(xué)習(xí)了解 React 的運(yùn)行機(jī)制,結(jié)合 React官方文檔,會(huì)更佳噢...
DEMO 目錄
- ReactDOM.render()
- Use Array in JSX
- 組件
- this.props.children
- PropTypes
- 獲取真實(shí)的 DOM 節(jié)點(diǎn)
- this.state
- 表單
- 組件的生命周期
- 使用 Promise 獲取 Github 的數(shù)據(jù)
- Todo List
- 井字棋(Tic Tac Toe)
引入資源
<div id="output"></div>
<!-- Load Babel -->
<!-- v6 <script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script> -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<!-- Your custom script here -->
<script type="text/babel">
const getMessage = () => "Hello World";
document.getElementById('output').innerHTML = getMessage();
</script>
Demo01: ReactDOM.render()
初始化咱先 Hello 一下,使用 jsx 語(yǔ)法,碰到代碼塊使用({ })包起來(lái),碰到 html 標(biāo)簽,就使用(< />):
var names = ["AAA", "BBB", "CCC"];
ReactDOM.render(
<div>
{names.map(function(name) {
return <h2>Hello, {name}!</h2>;
})}
</div>,
document.getElementById("example")
);
Demo02: Use Array in JSX
如果 JavaScript 的變量是個(gè)數(shù)組,會(huì)展開(kāi)這個(gè)數(shù)組的所有項(xiàng).
var arr = [<h1 key="h1">Hello,</h1>, <h2 key="h2">React is awesome!</h2>];
ReactDOM.render(<div>{arr}</div>, document.getElementById("example"));
Demo03: 組件
變量 HelloMsg 是一個(gè)組件類。模板插入 <HelloMsg /> 時(shí),會(huì)自動(dòng)生成 HelloMsg 的一個(gè)實(shí)例。所有組件類都必須有自己的 render 方法,用于輸出組件。
class HelloMsg extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
ReactDOM.render(
<HelloMsg name="Dataozi" />,
document.getElementById("example")
);
Demo04: this.props.children
this.props 對(duì)象的屬性與組件的屬性一一對(duì)應(yīng),但是有一個(gè)例外,就是 this.props.children 屬性。
ps: 注意大小寫(xiě) React.Children、React.Component
class NotesList extends React.Component {
render() {
return (
<ol>
{React.Children.map(this.props.children, function(child) {
return <li>{child}</li>;
})}
</ol>
);
}
}
ReactDOM.render(
<NotesList>
<span>Hello</span>
<span>World</span>
<span>React</span>
</NotesList>,
document.getElementById("example")
);
Demo05: PropTypes
React 內(nèi)置了一些類型檢查的功能。要在組件的 props 上進(jìn)行類型檢查,你只需配置特定的 propTypes 屬性:
var data = {
tilte: "Hello",
age: 19,
isStudent: true
};
class MyTitle extends React.Component {
static propTypes = {
tilte: PropTypes.string,
age: PropTypes.number,
isStudent: PropTypes.bool
};
render() {
return (
<div>
<h1>{this.props.data.tilte}</h1>
<h2>{this.props.data.age}</h2>
<h3>{this.props.data.isStudent ? "Yes" : "No"}</h3>
</div>
);
}
}
ReactDOM.render(<MyTitle data={data} />, document.getElementById("root"));
還可以通過(guò)配置特定的 defaultProps 屬性來(lái)定義 props 的默認(rèn)值:
class DefaultTitle extends React.Component {
render() {
return <h4>{this.props.title}</h4>;
}
}
//指定 props 的默認(rèn)值:
DefaultTitle.defaultProps = {
title: "Hello React!"
};
ReactDOM.render(<DefaultTitle />, document.getElementById("root2"));
Demo06: 獲取真實(shí)的 DOM 節(jié)點(diǎn)
Refs 提供了一種方式,允許我們?cè)L問(wèn) DOM 節(jié)點(diǎn)或在 render 方法中創(chuàng)建的 React 元素。
- 創(chuàng)建 Refs: Refs 是由
React.createRef()創(chuàng)建的,并通過(guò) ref 屬性附加到 React 元素(比如 input) - 訪問(wèn) Refs: 當(dāng) ref 被傳遞給 render 中的元素時(shí),對(duì)該節(jié)點(diǎn)的引用可以在 ref 的 current 屬性中被訪問(wèn),
this.myTextFocus.current.focus();
你不能在函數(shù)組件上使用 ref 屬性,因?yàn)樗鼈儧](méi)有實(shí)例
組件 MyComponent 的子節(jié)點(diǎn)有一個(gè)文本輸入框,用于獲取用戶的輸入。這時(shí)就必須獲取真實(shí)的 DOM 節(jié)點(diǎn),虛擬 DOM 是拿不到用戶輸入的。
class MyComponent extends React.Component {
constructor(props) {
super(props);
// 創(chuàng)建一個(gè) ref 來(lái)存儲(chǔ) myTextFocus 的 DOM 元素
this.myTextFocus = React.createRef();
this.handerClick = this.handerClick.bind(this);
}
handerClick() {
// 直接使用原生 API 使 text 輸入框獲得焦點(diǎn)
// 通過(guò) "current" 來(lái)訪問(wèn) DOM 節(jié)點(diǎn)
this.myTextFocus.current.focus();
}
render() {
// 告訴 React 我們想把 <input> ref 關(guān)聯(lián)到
// 構(gòu)造器里創(chuàng)建的 `myTextFocus` 上
return (
<div>
<input type="text" ref={this.myTextFocus} />
<input type="button" value="點(diǎn)擊聚焦" onClick={this.handerClick} />
</div>
);
}
}
ReactDOM.render(<MyComponent />, document.getElementById("root"));
Demo07: this.state
學(xué)習(xí)如何封裝真正可復(fù)用的 Clock 組件。它將設(shè)置自己的計(jì)時(shí)器并每秒更新一次。
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { date: new Date() }; //為 this.state 賦初值
}
componentDidMount() {
// Clock初次被渲染到DOM時(shí),為?其掛載一個(gè)計(jì)時(shí)器
this.timerID = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
// Clock被刪除時(shí),卸載其計(jì)時(shí)器
clearInterval(this.timerID);
}
tick() {
// 使用 this.setState() 來(lái)時(shí)刻更新組件 state
this.setState({ date: new Date() });
}
render() {
return (
<div>
<h1>Hello, React!</h1>
<h2>現(xiàn)在是北京時(shí)間:{this.state.date.toLocaleTimeString()}</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
Demo08: 表單
受控組件:渲染表單的 React 組件還控制著用戶輸入過(guò)程中表單發(fā)生的操作,被 React 以這種方式控制取值的表單輸入元素就叫做“受控組件”。
即:表單數(shù)據(jù)是由 React 組件來(lái)管理的。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {
// 唯一數(shù)據(jù)源
value: ""
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({
value: event.target.value // 顯示的值將隨著用戶輸入而更新
});
}
handleSubmit(event) {
if (this.state.value) {
alert("接受到的name值是:" + this.state.value);
}
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input
type="text"
value={this.state.value}
onChange={this.handleChange}
/>
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(<NameForm />, document.getElementById("root"));
非受控組件:表單數(shù)據(jù)將交由 DOM 節(jié)點(diǎn)來(lái)處理,即使用 ref 來(lái)從 DOM 節(jié)點(diǎn)中獲取表單數(shù)據(jù)
class NameForm extends React.Component {
constructor(props) {
super(props);
this.input = React.createRef();
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
alert("接受到的name值是:" + this.input.current.value);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit}>
<input type="text" ref={this.input} />
<input type="submit" value="提交" />
</form>
);
}
}
ReactDOM.render(<NameForm />, document.getElementById("root"));
Demo09: 組件的生命周期
- 組件的生命周期
- 生命周期圖譜速查表
- React 的生命周期 --- Ant Design 語(yǔ)雀
主要路線順序:掛載 - 更新 - 卸載 - 錯(cuò)誤處理
掛載
當(dāng)組件實(shí)例被創(chuàng)建并插入 DOM 中時(shí),其生命周期調(diào)用如下:
- consctructor() --- React 組件的構(gòu)造函數(shù),不初始化 state 或不進(jìn)行方法綁定,則不需要
- static getDerivedStateFromProps() --- 不常用
- render() --- 唯一必須實(shí)現(xiàn)的方法,并且應(yīng)該是純函數(shù)
- componentDidMount() --- 依賴于 DOM 節(jié)點(diǎn)的初始化應(yīng)該在這里
更新
當(dāng)組件的 props 或 state 發(fā)生變化時(shí),會(huì)觸發(fā)更新:
- static getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate() --- 不常用
- componentDidUpdate() --- 在更新后會(huì)被立即調(diào)用
卸載
當(dāng)組件從 DOM 中移除時(shí):
- componentWillUnmount() --- 會(huì)在組件卸載及銷毀之前直接調(diào)用
錯(cuò)誤處理
當(dāng)渲染過(guò)程,生命周期,或子組件的構(gòu)造函數(shù)中拋出錯(cuò)誤時(shí):
- static getDerivedStateFromError()
- componentDidCatch()
過(guò)期的生命周期方法:
- UNSAFE_componentWillMount() --- 掛載前調(diào)用,目前使用 constructor()初始化 state
- UNSAFE_componentWillReceiveProps()
- UNSAFE_componentWillUpdate()
class Hello extends React.Component {
constructor(props) {
super(props);
this.state = {
fontSize: 12,
opacity: 0.01
};
}
componentDidMount() {
this.timerID = setInterval(() => {
let opacity = this.state.opacity;
let fontSize = this.state.fontSize;
opacity += 0.02;
fontSize += 1;
if (opacity >= 1) {
opacity = 0.01;
}
if (fontSize >= 63) {
fontSize = 12;
}
this.setState({
fontSize,
opacity
});
}, 100);
}
componentWillUnmount() {
clearInterval(this.timerID);
}
render() {
return (
<h1
style={{ opacity: this.state.opacity, fontSize: this.state.fontSize }}
>
Hello, {this.props.name}
</h1>
);
}
}
ReactDOM.render(<Hello name="React" />, document.getElementById("root"));
Demo10: 使用 Promise 獲取 Github 的數(shù)據(jù)
ReactDOM.render(
<ReportList
promise={$.getJSON(
"https://api.github.com/search/repositories?q=javascript&sort=stars"
)}
/>,
document.getElementById("root")
);
從 Github 的 API 抓取數(shù)據(jù),然后將 Promise 對(duì)象作為屬性,傳給 ReportList 組件。
如果 Promise 對(duì)象正在抓取數(shù)據(jù)(pending 狀態(tài)),組件顯示"loading...";
如果 Promise 對(duì)象報(bào)錯(cuò)(rejected 狀態(tài)),組件顯示報(bào)錯(cuò)信息;
如果 Promise 對(duì)象抓取數(shù)據(jù)成功(fulfilled 狀態(tài)),組件顯示獲取的數(shù)據(jù)。
在這里查看完整 Demo/源碼 --- 谷歌瀏覽器有時(shí)候會(huì)報(bào)跨域的問(wèn)題,可以使用火狐等瀏覽器試看
接下來(lái)來(lái)幾個(gè)混合實(shí)戰(zhàn)吧
Demo11: Todo List
主要練習(xí)使用 props 和 state,使用 state 保存現(xiàn)有的待辦事項(xiàng)列表及用戶的一些操作(刪除、完成)等。
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
items: []
};
this.addItem = this.addItem.bind(this);
this.deleteItem = this.deleteItem.bind(this);
this.doneItem = this.doneItem.bind(this);
}
addItem(item) {
const newItem = {
text: item.text,
id: Date.now(),
done: false
};
this.setState({
items: this.state.items.concat(newItem)
});
}
deleteItem(index) {
this.state.items.splice(index, 1);
this.setState({
items: this.state.items
});
}
doneItem(index) {
const items = this.state.items;
const todo = items[index];
items.splice(index, 1);
todo.done = !todo.done;
todo.done ? items.unshift(todo) : items.push(todo);
this.setState({ items });
}
render() {
return (
<div className="container">
<h1>TODO</h1>
<TodoList
items={this.state.items}
deleteClick={this.deleteItem}
doneClick={this.doneItem}
/>
<TodoForm addItem={this.addItem} items={this.state.items} />
</div>
);
}
}
Demo12: 井字棋(Tic Tac Toe)
tic-tac-toe(三連棋)游戲的功能
- [x] 能夠判定玩家何時(shí)獲勝
- [x] 能夠記錄游戲進(jìn)程
- [x] 允許玩家查看游戲的歷史記錄,也可以查看任意一個(gè)歷史版本的游戲棋盤狀態(tài)
- [x] 在游戲歷史記錄列表顯示每一步棋的坐標(biāo),格式為 (列號(hào), 行號(hào))
- [x] 在歷史記錄列表中加粗顯示當(dāng)前選擇的項(xiàng)目
- [ ] 使用兩個(gè)循環(huán)來(lái)渲染出棋盤的格子,而不是在代碼里寫(xiě)死(hardcode)
- [ ] 添加一個(gè)可以升序或降序顯示歷史記錄的按鈕
- [ ] 每當(dāng)有人獲勝時(shí),高亮顯示連成一線的 3 顆棋子
- [x] 當(dāng)無(wú)人獲勝時(shí),顯示一個(gè)平局的消息
學(xué)習(xí)資料
- React 入門實(shí)例教程 --- 阮一峰
- react-demos