jsx變爲js的過程:http://babeljs.io/repl/javascript
youtube: https://www.youtube.com/channel/UCA-Jkgr40A9kl5vsIqg-BIg/playlistscss
raisl365: https://www.rails365.net/movies/you-ren-de-react-shi-pin-jiao-cheng-ji-chu-pian-1-jie-shaohtml
上一節講了:經過代碼的分離來展現client.js文件的進化過程。前端
關於基礎,還須要繼續夯實。java
先考慮下組件的生命週期node
Ref: 基礎篇 #14 組件生命週期(完結), 對應代碼:https://github.com/hfpp2012/hello-reactreact
Ref: [React] 02 - Intro: why reac and its design pattern - React 組件生命週期android
生命週期的方法:git
componentWillMount 【掛載前的操做】在渲染前調用,在客戶端也在服務端。github
componentDidMount 【掛載後調用的操做】在第一次渲染後調用,只在客戶端。以後組件已經生成了對應的DOM結構,能夠經過this.getDOMNode()來進行訪問。 若是你想和其餘JavaScript框架一塊兒使用,能夠在這個方法中調用setTimeout, setInterval或者發送AJAX請求等操做(防止異部操做阻塞UI)。
componentWillReceiveProps 【接收prop後調用】在組件接收到一個新的 prop (更新後)時被調用。這個方法在初始化render時不會被調用。
shouldComponentUpdate 【幫助解決一些性能上的問題】返回一個布爾值。在組件接收到新的props或者state時被調用。在初始化時或者使用forceUpdate時不被調用。 能夠在你確認不須要更新組件時使用。
componentWillUpdate 在組件接收到新的props或者state但尚未render時被調用。在初始化時不會被調用。
componentDidUpdate 在組件完成更新後當即調用。在初始化時不會被調用。
componentWillUnmount 【作一些清除的動做】在組件從 DOM 中移除的時候馬上被調用。
官方api文檔:https://reactjs.org/docs/react-component.html
Goto: 生命週期流程圖
Mounting 第四個componentDidMount經常使用。
Updating 第四個componentDidUpdate經常使用。
其餘不太經常使用。
附加題:React Native 中 component 生命週期
(1) 這裏的 app 表明掛載點:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title></title> </head> <body> <div id="app"></div> <script type="text/babel"> ReactDOM.render( <h1>hello world</h1>, document.getElementById("app") ) </script> <script src="https://cdn.bootcss.com/react/16.2.0/umd/react.development.js"></script> <script src="https://cdn.bootcss.com/react-dom/16.2.0/umd/react-dom.development.js"></script> <script src="https://cdn.bootcss.com/babel-standalone/7.0.0-beta.3/babel.js"></script> </body> </html>
結果:生成了以下默認的文件,也就是默認空項目。
yarn start ----> 開啓3000端口,開始默認的react頁面。
yarn build ----> 獲得以下兩條命令提示
yarn global add serve
serve -s build
相繼執行兩命令,獲得以下圖:
默認監聽在5000端口,貌似跟上述的端口3000顯示的是同樣的內容。
以上,即是生成一個新項目到運行的過程。
$ npm start
unsw@unsw-UX303UB$ npm start
> hello-react@0.1.0 start /media/unsw/CloudStorage/Linux-pan/ExtendedTmpSpace/Android-Workplace/android-and-ml/React-Native/demo-react/rails365/hello-react-master
> react-scripts start
Starting the development server...
Compiled successfully!
You can now view hello-react in the browser.
Local: http://localhost:3000/
On Your Network: http://10.248.169.134:3000/
Note that the development build is not optimized.
To create a production build, use yarn build.
$ npm restart
unsw@unsw-UX303UB$ npm restart
> hello-react@0.1.0 start /media/unsw/CloudStorage/Linux-pan/ExtendedTmpSpace/Android-Workplace/android-and-ml/React-Native/demo-react/rails365/hello-react-master
> react-scripts start
Starting the development server...
Compiled successfully!
You can now view hello-react in the browser.
Local: http://localhost:3000/
On Your Network: http://10.248.169.134:3000/
Note that the development build is not optimized.
To create a production build, use yarn build.
首先
Bootstrap是美國Twitter公司的設計師Mark Otto和Jacob Thornton合做基於HTML、CSS、JavaScript 開發的簡潔、直觀、強悍的前端開發框架,使得 Web 開發更加快捷。
Bootstrap提供了優雅的HTML和CSS規範,它便是由動態CSS語言Less寫成,就是個「界面工具集」。
關於bootstrap文件包簡單介紹
文件名稱:
1. bootstrap.css 2. bootstrap.min.css 3. bootstrap-responsive.css 4. bootstrap-responsive.min.css 5. bootstrap.js 6. bootstrap.min.js
解釋:
1. bootstrap.css 是完整的bootstrap樣式表,未經壓縮過的,可供開發的時候進行調試用
2. bootstrap.min.css 是通過壓縮後的bootstrap樣式表,內容和bootstrap.css徹底同樣,可是把中間沒必要要的空格之類的東西都刪掉了,因此文件大小會比bootstrap.css小,能夠在部署網站的時候引用,若是引用了這個文件,就不必引用bootstrap.css了
3. bootstrap-responsive.css 這個是在對bootstrap框架應用了響應式佈局以後所須要的CSS樣式表,若是你的網站項目不許備作響應式設計,就不須要引用這個CSS。
4. bootstrap-responsive.min.css 和bootstrap.min.css的做用是同樣的,是bootstrap-responsive.css的壓縮版
5. bootstrap.js 這個是bootstrap的靈魂所在,是bootstrap的全部js指令的集合,你看到bootstrap裏面全部的js效果,都是由這個文件控制的,這個文件也是一個未經壓縮的版本,供開發的時候進行調試用
6. bootstrap.min.js 是bootstrap.js的壓縮版,內容和bootstrap.js同樣的,可是文件大小會小不少,在部署網站的時候就能夠不引用bootstrap.js,而換成引用這個文件了~~
對於Bootstrap的認識,看來須要將菜鳥教程概覽一遍:Bootstrap 教程
其次
代碼:https://github.com/hfpp2012/hello-react
查看組件的層次、各個組件的Props、States等信息,演示以下:
app.js的內容,也就是標籤<app>實際掛在的地方。
render() { const user = { name: "Anna", hobbies: ["Sports", "Reading"] } let homeCmp = ""; if (this.state.homeMounted) { homeCmp = ( <Home name={"Max"} initialAge={12} user={user} greet={this.onGreet} changeLink={this.onChangeLinkName.bind(this)} initialName={this.state.homeLink} /> ); }
return (
/**
* 一段普通的html內容
*/ <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Header homeLink={this.state.homeLink} /> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>Hello !!</h1> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> {homeCmp} </div> </div> <hr /> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <button onClick={this.onChangeHomeMounted.bind(this)} className="btn btn-primary">(Un)mount Home Component</button> </div> </div> </div>
); }
穩定、快速、免費的前端開源項目 CDN 加速服務。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="theme-color" content="#000000"> <!-- manifest.json provides metadata used when your web app is added to the homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"> <link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico"> <link href="https://cdn.bootcss.com/bootstrap/4.0.0-beta.2/css/bootstrap.css" rel="stylesheet"> // insert this url.
這裏有兩個獨立的組件,利於項目分工:
1. Header.js
2. Home.js
而後在App.js中引用了這兩個組件。
略,沒啥可說的。
(6) 組件間消息傳遞
自定義控件,如何得到組件的「參數」和組件內部的「子組件」?
父組件
import React, { Component } from 'react'; import Header from './components/Header'; import Home from './components/Home'; class App extends Component { render() {
const user = { // <---- 這是一個對象 name : "Anna", hobbies: ["Sports", "Reading"] // <---- 遍歷是須要map }
return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Header /> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>Hello !!</h1> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Homename={"Max"} age={12} user={user}> // <---- 向組件傳入三個參數,經過prop做爲媒婆 <p>I am child</p> // <---- 這算是Home的Children節點 </Home> </div> </div> </div> ); } } export default App;
爲了防止參數的自動類型轉化,能夠執行參數強制檢查的策略。
例如,但願是數字,但可能傳入子組件後,自動變成了字符串,搞得咱們沒有辦法拿來作算術運算了呢。
子組件
import React, { Component } from 'react'; import PropTypes from 'prop-types'; // <---- 類型轉化 export default class Home extends Component { render() { return (
<div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <div>your name is {this.props.name}, your age is {this.props.age}</div> <div> <h4>hobbies</h4> <ul> {this.props.user.hobbies.map((hobby, i) => <li key={i}>{hobby}</li>)} </ul> </div> <div>{this.props.children}</div> // <---- 此處得到了"I am child" </div> </div> </div> ); } }
/**
* 在此定義參數的類型
*/ Home.propTypes = { name: PropTypes.string, age: PropTypes.number, user: PropTypes.object, children: PropTypes.element.isRequired };
(7) 事件定義、觸發
注意:如下方案只是改變了this.age的值,但沒有及時的使UI渲染。
export default class Home extends Component { constructor(props) { super(props); this.age = this.props.age; } onMakeOlder() { this.age += 3; console.log(this); } render() { return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <div>your name is {this.props.name}, your age is {this.props.age}</div>
<button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button>
{this.onMakeOlder.bind(this)} // --> 這個方案須要寫bind,費事兒
</div> </div> </div> ); } }
(8) state屬性及時渲染
由於:
constructor(props) { super(props); this.state = { age: props.initialAge } }
因此:
{this.props.age} 變爲 {this.state.age}
-----------------------------------------
onMakeOlder() {
this.setState({
age: this.state.age + 3
})
}
(9) 虛擬DOM
使用的是DIFF Algorithm。
(10) 無狀態組件
Ref: 基礎篇 #10 無狀態組件
簡單的代碼重構,以下。
import React from 'react';
(1)
export class Header extends Component {
render() {
/**
* <好處>
* 不用再聲明類
* 不要顯式聲明this關鍵字
* 更簡潔,佔用內存更小,能夠寫成無反作用的純函數
*/
(2) const Header = (props) => {
---------------------------------------------------- return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>Header</h1> </div> </div> </div> ); }; export default Header;
無狀態組件
1. 無需state, 不處理用戶的輸入,組件全部的數據都是依賴props傳入。
2. 不須要用到生命週期函數。
高階組件HOC
會返回組件的組件,Redux就是一個實現例子,可處理狀態。
(11) 子組件向父組件傳值
import React, { Component } from 'react'; import Header from './components/Header'; import Home from './components/Home'; class App extends Component {
onGreet(age) { alert(age); } render() { const user = { name: "Anna", hobbies: ["Sports", "Reading"] }
return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Header /> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>Hello !!</h1> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Homename={"Max"} initialAge={12} user={user} greet={this.onGreet} /> // onGreet做爲參數傳遞給子組件 </div> </div> </div> ); } } export default App;
實驗:子組件調用父組件的「方法」。
Home.js,其中的Greet button觸發了App組件中的函數,是怎麼作到的?
/**
* 1. 點擊 子組件的button, 點擊後觸發 該函數handleGreet
* 2. 該函數實際上是個父附件的函數指針
* 如此,子組件經過父組件給他的一個接口從而改變了父組件自身
*/
handleGreet() { this.props.greet(this.state.age) // 其實就是父組件中的onGreet }
render() { return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <div>your name is {this.props.name}, your age is {this.state.age}</div> <p>Status: {this.state.status}</p> <button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button> <hr />
<button onClick={this.handleGreet.bind(this)} className="btn btn-primary">Greet</button>
</div> </div> </div> ); } } Home.propTypes = { name: PropTypes.string, age: PropTypes.number, user: PropTypes.object, greet: PropTypes.func // <---- 添加對應的參數檢測 };
Ref: 爲何React事件處理函數必須使用Function.bind()綁定this?
經典考題
let obj = {
tmp: 'Yes!',
testLog: function(){
console.log(this.tmp);
}
};
obj.testLog(); # 正常顯示‘yes!'
-------------------------------
let tmpLog = obj.testLog; tmpLog(); # 過分了一下,而後testLog內部的this就變成了」全局this",在這裏也就是window
思考:畢竟每個對象都有本身的this,tmpLog雖然被設置成爲了obj.testLog,但本身仍然保留了本身的this。
背後的原理
React跟原生JavaScript的事件綁定區別有兩點,其中第二點就是:
在React(或者說JSX)中,傳遞的事件參數不是一個字符串,而是一個實實在在的函數:
這樣說,React中的事件名(上圖中的onClick)就是我所舉例子中的中間變量,React在事件發生時調用onClick,因爲onClick只是中間變量,因此處理函數中的this指向會丟失;
爲了解決這個問題,咱們須要在實例化對象的時候,須要在構造函數中綁定this,使得不管事件處理函數如何傳遞,它的this的指向都是固定的,固定指向咱們所實例化的對象。
(12) 子組件之間的傳值
子組件 Home 與 header 間的通訊,該怎麼搞? ---- 固然是經過父組件」搭橋「。
父組件App.js
import React, { Component } from 'react'; import Header from './components/Header'; import Home from './components/Home'; class App extends Component {
constructor() { super(); this.state = { homeLink: "Home" } } onGreet(age) { alert(age); } onChangeLinkName(newName) { // (5) 實際幹活的地方,也就是改變了state this.setState({ homeLink: newName }) } render() {
const user = { name: "Anna", hobbies: ["Sports", "Reading"] }
return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <HeaderhomeLink={this.state.homeLink} /> // (6) 在這裏,homelink對於Header子組件就是一個參數;參數被另外一個子組件改變,以後,state改變,天然會觸發這個子組件UI更新 </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>Hello !!</h1> </div> </div> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <Home name ={"Max"} initialAge={12} user ={user} greet ={this.onGreet} changeLink={this.onChangeLinkName.bind(this)} // (4) app 給子組件傳遞個「函數指針「,改變的state實際上是屬於父組件的東西(state),homelink是屬於父組件 /> </div> </div> </div> ); } } export default App;
子組件Header.js
Jeff: 做爲父組件的一個子部分,看似return出html就行了,沒用到render。
只須要顯示,因此就搞成」無狀態組件「就好啦,不須要render。
import React from 'react'; const Header = (props) => { return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <h1>{props.homeLink}</h1> </div> </div> </div> ); }; export default Header;
子組件Home.js
Jeff: 有交互,因此須要render。
import React, { Component } from 'react'; import PropTypes from 'prop-types'; export default class Home extends Component { constructor(props) { super(props); this.state = { age: props.initialAge, status: 0, homeLink: "Changed Link" // (3) 兄弟子組件得到的改變值,也就是父組件爲他倆搭的橋 } setTimeout(() => { this.setState({ status: 1 }) }, 3000) } onMakeOlder() { this.setState({ age: this.state.age + 3 }) } handleGreet() { this.props.greet(this.state.age) } onChangeLink() { this.props.changeLink(this.state.homeLink); // (2) 執行了「指針函數」 from 父組件;注意參數,子組件會將input的值拿來做爲這裏的value。 } render() { return ( <div className="container"> <div className="row"> <div className="col-xs-1 col-xs-offset-11"> <div>your name is {this.props.name}, your age is {this.state.age}</div> <p>Status: {this.state.status}</p> <button onClick={() => {this.onMakeOlder()}} className="btn btn-primary">Make me older</button> <hr /> <button onClick={this.handleGreet.bind(this)} className="btn btn-primary">Greet</button> <hr /> <button onClick={this.onChangeLink.bind(this)} class="btn btn-primary">Change Header Link</button> // (1) 點擊觸發 </div> </div> </div> ); } } Home.propTypes = { name: PropTypes.string, age: PropTypes.number, user: PropTypes.object, greet: PropTypes.func };
(13) 雙向數據綁定
在以上的基礎上,加了input框;
這裏會讀入「輸入框」的內容,而後再改寫Header的內容。
<input type ="text" defaultValue={this.props.initialName} value ={this.state.initialName} onChange ={(event) => this.onHandleChange(event)} // (1) 只是改變了子組件home內部的一個state;奇怪,這裏的event表明了什麼? /> <button onClick={this.onChangeLink.bind(this)} className="btn btn-primary">Change Header Link</button> // (2) 點擊後,讀出這個home內部的state,而後改寫父組件的state
event 表明了什麼?event.target可得到事件涉及組件的屬性。
onHandleChange(event) {
this.setState({
homeLink: event.target.value
})
}
(14) 組件生命週期
git clone https://github.com/hfpp2012/hello-react.git
class Show中沒有state,因此課省略掉constructor,系統會自動採用默認的。
下圖也展現了組件的可複製性,方便代碼複用。