[React] 09 - Tutorial: components

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

  • Mounting     :已插入真實 DOM 【掛載】
  • Updating      :正在被從新渲染  【更新】
  • Unmounting:已移出真實 DOM 【卸載】

 

生命週期的方法: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>

 

(2) react 應用最好用的腳手架 create-react-app

  • 建立項目
  1. 先安裝nodejs
  2. 再安裝create-react-app這條命令
  3. 以後經過該命令生成項目hello-react

 

結果:生成了以下默認的文件,也就是默認空項目。 

 

 

  •  下一步導航提示

 

  • 開啓開發環境的服務器

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.
View Code

$ 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.
View Code

 

(3) bootstrap好看的組件

首先

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

  • 安裝react開發插件:React Developer Tools

查看組件的層次、各個組件的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>

); }

 

  • 利用BootCDN寫組件 - CDN 加速服務

穩定、快速、免費的前端開源項目 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.

 

(4) 多個組件的開發

這裏有兩個獨立的組件,利於項目分工:

 1. Header.js

 2. Home.js

而後在App.js中引用了這兩個組件。

  

(5) 輸出動態數據

略,沒啥可說的。

 

(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  // <---- 添加對應的參數檢測 };

 

  • 爲什麼要bind(this)? 

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,系統會自動採用默認的。

下圖也展現了組件的可複製性,方便代碼複用。 

相關文章
相關標籤/搜索