React學習筆記(二)—— JSX、組件與生命週期

一、JSX

1.1、什麼是JSX?

JSX = JavaScript XML,這是React官方發明的一種JS語法(糖)

概念:JSX是 JavaScript XML(HTML)的縮寫,表示在 JS 代碼中書寫 HTML 結構

設想如下變量聲明:

const element = <h1>Hello, world!</h1>;

這個有趣的標籤語法既不是字符串也不是 HTML。

它被稱爲 JSX,是一個 JavaScript 的語法擴展。我們建議在 React 中配合使用 JSX,JSX 可以很好地描述 UI 應該呈現出它應有交互的本質形式。JSX 可能會使人聯想到模板語言,但它具有 JavaScript 的全部功能。

JSX 可以生成 React “元素”。

1.2、爲什麼使用 JSX?

React 認爲渲染邏輯本質上與其他 UI 邏輯內在耦合,比如,在 UI 中需要綁定處理事件、在某些時刻狀態發生變化時需要通知到 UI,以及需要在 UI 中展示準備好的數據。

React 並沒有採用將標記與邏輯分離到不同文件這種人爲的分離方式,而是通過將二者共同存放在稱之爲“組件”的鬆散耦合單元之中,來實現關注點分離。我們將在後面章節中深入學習組件。如果你還沒有適應在 JS 中使用標記語言,這個會議討論應該可以說服你。

React 不強制要求使用 JSX,但是大多數人發現,在 JavaScript 代碼中將 JSX 和 UI 放在一起時,會在視覺上有輔助作用。它還可以使 React 顯示更多有用的錯誤和警告消息。

瀏覽器默認是不支持JSX的,所以jsx語法必須使用@babel/preset-react進行編譯,編譯的結果React.createElement()這種Api的代碼。

示例:<div>Hello</div> ==>@babel/preset-react==> React.createElement('div',{},'Hello')

在React開發中,JSX語法是可選的(也就是你可以不使用JSX)。如果不使用JSX語法,React組件代碼將變得特別麻煩(難以維護)。所以幾乎所有React開發都用的是JSX語法。

JSX是Javascript的一種語法拓展

JSX是JavaScript XML簡寫,表示在JavaScript中編寫XML格式代碼(也就是HTML格式)

優勢:

    • 聲明式語法更加直觀
    • 與HTML結構相同
    • 降低了學習成本、提升開發效率

注意:JSX 並不是標準的 JS 語法,是 JS 的語法擴展,瀏覽器默認是不識別的,腳手架中內置的 @babel/plugin-transform-react-jsx 包,用來解析該語法

1.3、JSX中使用js表達式

目標任務: 能夠在JSX中使用表達式

語法

{ JS 表達式 }

const name = '張三'

<h1>你好,我叫{name}</h1>   //    <h1>你好,我叫張三</h1> 

可以使用的表達式

  1. 字符串、數值、布爾值、null、undefined、object( [] / {} )
  2. 1 + 2、'abc'.split('')、['a', 'b'].join('-')
  3. 內置函數,自定義函數

特別注意

​ if 語句/ switch-case 語句/ 變量聲明語句,這些叫做語句,不是表達式,不能出現在 {} 中。

可以使用?:與&&替代if的功能

在下面的示例中,我們將調用 JavaScript 函數 formatName(user) 的結果,並將結果嵌入到 <h1> 元素中。

function formatName(user) {
  return user.firstName + ' ' + user.lastName;
}

const user = {
  firstName: 'Harper',
  lastName: 'Perez'
};

const element = (
  <h1>
    Hello, {formatName(user)}!  </h1>
);

爲了便於閱讀,我們會將 JSX 拆分爲多行。同時,我們建議將內容包裹在括號中,雖然這樣做不是強制要求的,但是這可以避免遇到自動插入分號陷阱。

1.4. JSX列表渲染

1.4.1、map函數

map()方法定義在JavaScript的Array中,它返回一個新的數組,數組中的元素爲原始數組調用函數處理後的值。
注意:

    • map()不會對空數組進行檢測
    • map()不會改變原始數組
array.map(function(currentValue, index, arr), thisValue)

參數說明:

  • function(currentValue, index, arr):必須。爲一個函數,數組中的每個元素都會執行這個函數。其中函數參數:
  1. currentValue:必須。當前元素的的值。
  2. index:可選。當前元素的索引。
  3. arr:可選。當前元素屬於的數組對象。
  • thisValue:可選。對象作爲該執行回調時使用,傳遞給函數,用作"this"的值。
//返回由原數組中每個元素的平方組成的新數組:

let array = [1, 2, 3, 4, 5];

let newArray = array.map((item) => {
    return item * item;
})

console.log(newArray)  // [1, 4, 9, 16, 25]

 

1.4.2、JSX列表渲染

JSX 表達式必須具有一個父元素。沒有父元素時請使用<></>

目標任務: 能夠在JSX中實現列表渲染

頁面的構建離不開重複的列表結構,比如歌曲列表,商品列表等,我們知道vue中用的是v-for,react這邊如何實現呢?

實現:使用數組的map 方法

案例:

// 列表
const songs = [
  { id: 1, name: '癡心絕對' },
  { id: 2, name: '像我這樣的人' },
  { id: 3, name: '南山南' }
]

function App() {
  return (
    <div className="App">
      <ul>
        {
          songs.map(item => <li>{item.name}</li>)
        }
      </ul>
    </div>
  )
}

export default App

注意點:需要爲遍歷項添加 key 屬性

  1. key 在 HTML 結構中是看不到的,是 React 內部用來進行性能優化時使用
  2. key 在當前列表中要唯一的字符串或者數值(String/Number)
  3. 如果列表中有像 id 這種的唯一值,就用 id 來作爲 key 值
  4. 如果列表中沒有像 id 這種的唯一值,就可以使用 index(下標)來作爲 key 值

1.5、JSX條件渲染

目標任務: 能夠在JSX中實現條件渲染

作用:根據是否滿足條件生成HTML結構,比如Loading效果

實現:可以使用 三元運算符 或 邏輯與(&&)運算符

案例:

// 來個布爾值
const flag = true
function App() {
  return (
    <div className="App">
      {/* 條件渲染字符串 */}
      {flag ? 'react真有趣' : 'vue真有趣'}
      {/* 條件渲染標籤/組件 */}
      {flag ? <span>this is span</span> : null}
    </div>
  )
}
export default App

1.6. JSX樣式處理

目標任務: 能夠在JSX中實現css樣式處理

  • 行內樣式 - style

    import ReactDOM from "react-dom/client";

    //1、創建根節點
    let root = ReactDOM.createRoot(document.getElementById("root"));

    let songs = ["好漢歌", "南山南", "滴答"];

    //3、將Vnode渲染到根結點上
    root.render(
      <div>
        <h2 style={{ color: "red", backgroundColor: "yellow" }}>歌曲列表</h2>
        <ul>
          {songs.map((item) => (
            <li key={item}>{item}</li>
          ))}
        </ul>
      </div>
    );
  • 行內樣式 - style - 更優寫法

    const styleObj = {
        color:red
    }
    
    function App() {
      return (
        <div className="App">
          <div style={ styleObj }>this is a div</div>
        </div>
      )
    }
    
    export default App
    
  • 類名 - className(推薦)

    app.css

    .title {
      font-size: 30px;
      color: blue;
    }
    

    app.js

    import './app.css'
    
    function App() {
      return (
        <div className="App">
          <div className='title'>this is a div</div>
        </div>
      )
    }
    export default App
    
  • 類名 - className - 動態類名控制

    import './app.css'
    const showTitle = true
    function App() {
      return (
        <div className="App">
          <div className={ showTitle ? 'title' : ''}>this is a div</div>
        </div>
      )
    }
    export default App

1.7、注意事項

  1. JSX必須有一個根節點,如果沒有根節點,可以使用<></>(幽靈節點)替代
  2. 所有標籤必須形成閉合,成對閉合或者自閉合都可以
  3. JSX中的語法更加貼近JS語法,屬性名採用駝峯命名法 class -> className for -> htmlFor
  4. JSX支持多行(換行),如果需要換行,需使用() 包裹,防止bug出現

1.8、JSX 也是一個表達式

在編譯之後,JSX 表達式會被轉爲普通 JavaScript 函數調用,並且對其取值後得到 JavaScript 對象。

也就是說,你可以在 if 語句和 for 循環的代碼塊中使用 JSX,將 JSX 賦值給變量,把 JSX 當作參數傳入,以及從函數中返回 JSX:

function getGreeting(user) {
  if (user) {
    return <h1>Hello, {formatName(user)}!</h1>;  }
  return <h1>Hello, Stranger.</h1>;}

1.9、JSX 中指定屬性

你可以通過使用引號,來將屬性值指定爲字符串字面量:

const element = <a href="https://www.reactjs.org"> link </a>;

也可以使用大括號,來在屬性值中插入一個 JavaScript 表達式:

const element = <img src={user.avatarUrl}></img>;

在屬性中嵌入 JavaScript 表達式時,不要在大括號外面加上引號。你應該僅使用引號(對於字符串值)或大括號(對於表達式)中的一個,對於同一屬性不能同時使用這兩種符號。

警告:

因爲 JSX 語法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 camelCase(小駝峯命名)來定義屬性的名稱,而不使用 HTML 屬性名稱的命名約定。

例如,JSX 裏的 class 變成了 className,而 tabindex 則變爲 tabIndex

屬性也可以是一個箭頭函數:

import ReactDOM from "react-dom/client";
import React from "react";
import "./App.css";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let counter = 0;
const element = (
  <div>
    <h2>計算器</h2>
    <button
      onClick={() => {
        counter++;
        console.log(counter);
      }}
    >
      點擊加1
    </button>
  </div>
);

//3、將Vnode渲染到根結點上
root.render(element);

也可以是一個普通函數:

import ReactDOM from "react-dom/client";
import React from "react";
import "./App.css";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let counter = 0;
const element = (
  <div>
    <h2>計算器</h2>
    <button onClick={increment}>點擊加1</button>
  </div>
);

function increment() {
  counter++;
  console.log(counter);
}

//3、將Vnode渲染到根結點上
root.render(element);

1.10、使用 JSX 指定子元素

假如一個標籤裏面沒有內容,你可以使用 /> 來閉合標籤,就像 XML 語法一樣:

const element = <img src={user.avatarUrl} />;

JSX 標籤裏能夠包含很多子元素:

const element = (
  <div>
    <h1>Hello!</h1>
    <h2>Good to see you here.</h2>
  </div>
);

1.11、JSX 防止注入攻擊

你可以安全地在 JSX 當中插入用戶輸入內容:

const title = response.potentiallyMaliciousInput;
// 直接使用是安全的:
const element = <h1>{title}</h1>;

React DOM 在渲染所有輸入內容之前,默認會進行轉義。它可以確保在你的應用中,永遠不會注入那些並非自己明確編寫的內容。所有的內容在渲染之前都被轉換成了字符串。這樣可以有效地防止 XSS(cross-site-scripting, 跨站腳本)攻擊。

1.12、JSX 表示對象

Babel 會把 JSX 轉譯成一個名爲 React.createElement() 函數調用。

以下兩種示例代碼完全等效:

const element = (
  <h1 className="greeting">
    Hello, world!
  </h1>
);
const element = React.createElement(
  'h1',
  {className: 'greeting'},
  'Hello, world!'
);

React.createElement() 會預先執行一些檢查,以幫助你編寫無錯代碼,但實際上它創建了一個這樣的對象:

// 注意:這是簡化過的結構
const element = {
  type: 'h1',
  props: {
    className: 'greeting',
    children: 'Hello, world!'
  }
};

這些對象被稱爲 “React 元素”。它們描述了你希望在屏幕上看到的內容。React 通過讀取這些對象,然後使用它們來構建 DOM 以及保持隨時更新。

import ReactDOM from "react-dom/client";
import React from "react";
import "./App.css";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

const element = React.createElement(
  "h1",
  { className: "blueBg" },
  "Hello, world!"
);

//3、將Vnode渲染到根結點上
root.render(element);

二、組件 Component

如果我們將一個頁面中所有的處理邏輯全部放在一起,處理起來就會變得非常複雜,而且不利於後續的管理以及擴展,但如果,我們將一個頁面拆分成一個個小的功能塊,每個功能塊完成屬於自己這部分獨立的功能,那麼之後整個頁面的管理和維護就變得非常容易了。如果我們將一個個功能塊拆分後,就可以像搭建積木一下來搭建我們的項目。
 
 

2.1、SPA

SPA指的是Single Page Application,就是隻有一張Web頁面的應用。單頁應用程序 (SPA) 是加載單個HTML 頁面並在用戶與應用程序交互時動態更新該頁面的Web應用程序。 瀏覽器一開始會加載必需的HTML、CSS和JavaScript,所有的操作都在這張頁面上完成,都由JavaScript來控制。因此,對單頁應用來說模塊化的開發和設計顯得相當重要。

單頁Web應用,顧名思義,就是隻有一張Web頁面的應用。瀏覽器一開始會加載必需的HTML、CSS和JavaScript,之後所有的操作都在這張頁面上完成,這一切都由JavaScript來控制。因此,單頁Web應用會包含大量的JavaScript代碼,複雜度可想而知,模塊化開發和設計的重要性不言而喻。

速度:更好的用戶體驗,讓用戶在web app感受native app的速度和流暢

MVVM:經典MVVM開發模式,前後端各負其責

ajax:重前端,業務邏輯全部在本地操作,數據都需要通過AJAX同步、提交

路由:在URL中採用#號來作爲當前視圖的地址,改變#號後的參數,頁面並不會重載

優點:

1.分離前後端關注點,前端負責View,後端負責Model,各司其職;
2.服務器只接口提供數據,不用展示邏輯和頁面合成,提高性能;
3.同一套後端程序代碼,不用修改兼容Web界面、手機;
4.用戶體驗好、快,內容的改變不需要重新加載整個頁面
5.可以緩存較多數據,減少服務器壓力
6.單頁應用像網絡一樣,幾乎隨處可以訪問—不像大多數的桌面應用,用戶可以通過任務網絡連接和適當的瀏覽器訪問單頁應用。如今,這一名單包括智能手機、平板電腦、電視、筆記本電腦和臺式計算機。

缺點:

1.SEO問題
2.剛開始的時候加載可能慢很多
3.用戶操作需要寫邏輯,前進、後退等
4.頁面複雜度提高很多,複雜邏輯難度成倍

2.2、什麼是組件?

組件允許你將 UI 拆分爲獨立可複用的代碼片段,並對每個片段進行獨立構思。

組件,從概念上類似於 JavaScript 函數。它接受任意的入參(即 “props”),並返回用於描述頁面展示內容的 React 元素。

組件它是一種抽象,允許我們使用小型、獨立和通常可複用的組件構建大型應用。仔細想想,幾乎任意類型的應用界面都可以抽象爲一個組件樹:

組件允許我們將 UI 劃分爲獨立的、可重用的部分,並且可以對每個部分進行單獨的思考。在實際應用中,組件常常被組織成層層嵌套的樹狀結構:

組件樹

2.3、組件定義

組件是 React的核心慨念,定 React應用程序的基石。組件將應用的UI拆分成獨立的、可複用的模塊,React 用任廳止定田一個一個組件搭建而成的。

定義一個組件有兩種方式,便用ES 6 class(類組件)和使用函數(函數組件)。我們先介紹使用class定義組件方式。

2.3.1、使用class定義組件

使用class定義組件需要滿足兩個條件:

(1)class繼承自 React.Component。

(2) class內部必須定義 render方法,render方法返回代表該組件UI的React元素。

使用create-react-app新建一個簡易BBS項目,在這個項目中定義一個組件PostList,用於展示BBS 的帖子列表。

PostList的定義如下:

import React, { Component } from "react";

class PostList extends Component {
  render() {
    return (
      <div>
        <h2>帖子列表:</h2>
        <ul>
          <li>男子喫霸王餐還教育老闆和氣生財</li>
          <li>莫斯科險遭無人機炸彈攻擊</li>
          <li>武漢官方把房地產歸爲困難行業</li>
        </ul>
      </div>
    );
  }
}

export default PostList;

index.js

import ReactDOM from "react-dom/client";
import React from "react";
import PostList from "./PostList";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let vNode = <PostList />;

//3、將Vnode渲染到根結點上
root.render(vNode);

運行效果:

2.3.2、使用函數定義組件

定義組件最簡單的方式就是編寫 JavaScript 函數:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

該函數是一個有效的 React 組件,因爲它接收唯一帶有數據的 “props”(代表屬性)對象與並返回一個 React 元素。這類組件被稱爲“函數組件”,因爲它本質上就是 JavaScript 函數。

index.js內容如下:

import ReactDOM from "react-dom/client";
import React from "react";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

//2、定義函數組件
function Welcome(props) {
  return <h2>Hello {props.name}!</h2>;
}

//3、使用組件
let vNode = <Welcome name="zhangguo" />;

//4、將Vnode渲染到根結點上
root.render(vNode);

運行結果:

 

約定說明

  1. 組件的名稱必須首字母大寫,react內部會根據這個來判斷是組件還是普通的HTML標籤

  2. 函數組件必須有返回值,表示該組件的 UI 結構;如果不需要渲染任何內容,則返回 null

  3. 組件就像 HTML 標籤一樣可以被渲染到頁面中。組件表示的是一段結構內容,對於函數組件來說,渲染的內容是函數的返回值就是對應的內容

  4. 使用函數名稱作爲組件標籤名稱,可以成對出現也可以自閉合

2.4、組件的props

2.3.1中的PostList 中的每一個帖子都使用一個標籤直接包裹,但一個帖子不僅包含能子的標題,還會包含帖子的創建人、帖子創建時間等信息,這時候標籤下的結構就會變得複雜。而且每一個帖子都需要重寫一次這 個複雜的結構,PostList 的結構將會變成類似這樣的形式:

import React, { Component } from "react";

class PostList extends Component {
  render() {
    return (
      <div>
        <h2>帖子列表:</h2>
        <ul>
          <li>
            <div>男子喫霸王餐還教育老闆和氣生財</div>
            <div>
              創建人:<span>小明</span>
            </div>
            <div>
              創建時間:<span>2023-02-03 18:19:22</span>
            </div>
          </li>
          <li>
            <div>莫斯科險遭無人機炸彈攻擊</div>
            <div>
              創建人:<span>小軍</span>
            </div>
            <div>
              創建時間:<span>2023-01-22 21:22:35</span>
            </div>
          </li>
          <li>
            <div>武漢官方把房地產歸爲困難行業</div>
            <div>
              創建人:<span>小華</span>
            </div>
            <div>
              創建時間:<span>2022-12-22 15:14:56</span>
            </div>
          </li>
        </ul>
      </div>
    );
  }
}

export default PostList;

但是,帖子列表的數括依然存在於 PostList中,如何將數據傳遞給每個 PostItem 組件呢?這時候就需要用到組件的props屬性。組件的 props用於把父組件中的數據或方法傳遞給子組件,供子組件使用。

props是一個簡單結構的對象,它包含的屬性正是由組件作爲JSX標籤使用時的屬性組成。

例如下面是一個使用User組

<User name='React' age='4' address=' America'>

此時User組件的 props結構如下:

props ={
name: 'React',
age: '4',
address: 'America'
}

利用props定義PostItem組件,PostItem.js如下所示:

import React, { Component } from "react";

class PostItem extends Component {
  render() {
    const { title, author, date } = this.props;
    return (
      <li>
        <div>{title}</div>
        <div>
          創建人:<span>{author}</span>
        </div>
        <div>
          創建時間:<span>{date}</span>
        </div>
      </li>
    );
  }
}

export default PostItem;

src/PostList.js

import React, { Component } from "react";
import PostItem from "./PostItem";

const data = [
  {
    title: "男子喫霸王餐還教育老闆和氣生財",
    author: "小明",
    date: "2023-02-03 18:19:22",
  },
  {
    title: "莫斯科險遭無人機炸彈攻擊",
    author: "小軍",
    date: "2023-01-22 21:22:35",
  },
  {
    title: "武漢官方把房地產歸爲困難行業",
    author: "小華",
    date: "2022-12-22 15:14:56",
  },
];

class PostList extends Component {
  render() {
    return (
      <div>
        <h2>帖子列表:</h2>
        <ul>
          {data.map((item) => (
            <PostItem
              title={item.title}
              author={item.author}
              date={item.date}
            />
          ))}
        </ul>
      </div>
    );
  }
}

export default PostList;

index.js

import ReactDOM from "react-dom/client";
import React from "react";
import PostList from "./PostList";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let vNode = <PostList />;

//3、將Vnode渲染到根結點上
root.render(vNode);

運行結果:

2.5、組件的state

組件的 state是組件內部的狀態,state的變化最終將反映到組件UI的上。我們在組件的構造方法constructor中通過this.state定義組件的初始狀態,並通過調用 this.setState 方法改變組件狀態(也是改變組件狀態的唯一方式),進而組件UI也會隨之重新渲染。

下面來改造下BBS項目。我們爲每一個帖子增加一個“點贊”按鈕每點擊一次,該帖子的點贊數增加1。點贊數是會發生變化的,它的變化也會影響到組件UI,因此我們將點贊數vote
作爲Postltem的一個狀態定義到它的state內。

修改後的PostItem:

import React, { Component } from "react";

class PostItem extends Component {
  constructor(props) {
    super(props); //調用Component的構造方法,用於完成React組件的初始化工作
    this.state = {
      vote: 0,
    };
  }

  //處理點擊邏輯
  handleClick() {
    let vote = this.state.vote;
    vote++;
    this.setState({ vote: vote });
  }

  render() {
    const { title, author, date } = this.props;
    return (
      <li>
        <div>{title}</div>
        <div>
          創建人:<span>{author}</span>
        </div>
        <div>
          創建時間:<span>{date}</span>
        </div>
        <div>
          <button
            onClick={() => {
              this.handleClick();
            }}
          >
            點贊 {this.state.vote}
          </button>
        </div>
      </li>
    );
  }
}

export default PostItem;

運行結果:

 

React組件正是由props和state兩種類型的數據驅動渲染出組件 UI。props是組件對外的接口,組件通過 props接收外部傳入的數據(包括方法); state是組件對內的接口,組件內部狀態的變化通過state反映。另外,props是隻讀的,你不能在組內部修改 props; state是可變的,組件狀態的變化通過修改state來實現。

2.6、有狀態組件和無狀態組件

是不是每個組件內部都需要定義state呢?當然不是。state用來反映組件內部狀態變化,如果一個組件的內部狀態是不變的,當然就用不到state,這樣的組件稱之爲無狀態組件,例如PostList。
反之,一個組件的內部狀態會發生變化,就需要使用state 來保存變化,這種組件稱爲有狀態組件,例如PostItem。
定義無狀態組件除了使用 ES 6 class的方式外,還可以使用函數定義,一個函數組件接收props作爲參數,返回代表這個組件的UI React 元素結構。
例如,下面是一個簡單的函數組件:

function Welcome (props) {
    return <h1>Hello, {props.name }</hl>;
}

可以看出,函數組件的寫法比類組件的寫法要簡潔很多,在使用無狀態組件時,應該儘量將其定義成函數組件

在開發React應用時,一定要先認真思考哪些組件應該設計成有狀態組件,哪些組件應該設計成無狀態組件。並且,應該儘可能多地使用無狀態組件,無狀態組件不用關心狀態的變化,只聚焦於UI的展示,因而更容易被複用。React應用組件設計的一般思路是,通過定義少數的有狀態組件管理整個應用的狀態變化,並且將狀態通過props傳遞給其餘的無狀態組件,由無狀態組件完成頁面絕大部分UI的渲染工作。總之,有狀態組件主要關注處理狀態變化的業務邏輯,無狀態組件主要關注組件UI的渲染。

下面讓我們回過頭來看一下BBS項目的組件設計。當前的組件設計並不合適,主要體現在:

(1)帖子列表通過一個常量data保存在組件之外,但帖子列表的數據增加或原有帖子的刪除都會導致帖子列表數據的變化。

(2)每一個 PostItem都維持個 vote狀態,但除了vote以外,帖子其他的信息(如標題、創建人等)都保存在PostList中,這顯然也是不合理的。

我們對這兩個組件進行重新設計,將PostList 設計爲有狀態組件,負責帖子列表數據的獲取以及點贊行爲的處理,將PostItem設計爲無狀態組件,只負責每一個帖子的
展示。

修改後的PostList.js:

import React, { Component } from "react";
import PostItem from "./PostItem";

class PostList extends Component {
  constructor(props) {
    super(props);
    this.state = {
      posts: [],
    };
    this.timer = null;
    //改變handleVote中的this指向
    this.handleVote = this.handleVote.bind(this);
  }

  data = [
    {
      id: 1,
      title: "男子喫霸王餐還教育老闆和氣生財",
      author: "小明",
      date: "2023-02-03 18:19:22",
      vote: 0,
    },
    {
      id: 2,
      title: "莫斯科險遭無人機炸彈攻擊",
      author: "小軍",
      date: "2023-01-22 21:22:35",
      vote: 0,
    },
    {
      id: 3,
      title: "武漢官方把房地產歸爲困難行業",
      author: "小華",
      date: "2022-12-22 15:14:56",
      vote: 0,
    },
  ];

  //組件掛載到DOM後
  componentDidMount() {
    this.timer = setTimeout(() => {
      this.setState({
        posts: this.data,
      });
    }, 1000);
  }

  //組件從DOM中卸載
  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  handleVote(id) {
    //遍歷所有的貼子,如果編號相同,則增加點贊後返回
    const posts = this.state.posts.map((item) =>
      item.id === id ? { ...item, vote: ++item.vote } : item
    );
    //重新設置狀態
    this.setState({
      posts,
    });
  }

  render() {
    return (
      <div>
        <h2>帖子列表:</h2>
        <ul>
          {this.state.posts.map((item) => (
            <PostItem post={item} onVote={this.handleVote} />
          ))}
        </ul>
      </div>
    );
  }
}

export default PostList;

修改後的PostItem.js:

import React from "react";

function PostItem(props) {
  const { post, onVote } = props;
  const handleVote = () => {
    onVote(post.id);
  };

  return (
    <li>
      <div>{post.title}</div>
      <div>
        創建人:<span>{post.author}</span>
      </div>
      <div>
        創建時間:<span>{post.date}</span>
      </div>
      <div>
        <button onClick={handleVote}>點贊 {post.vote}</button>
      </div>
    </li>
  );
}

export default PostItem;

運行效果:

 

2.6、屬性校驗和默認屬性

2.6.1.組件特殊屬性——propTypes

對Component設置propTypes屬性,可以爲Component的props屬性進行類型檢查。

import React, { Component } from "react";
import PropTypes from "prop-types";

class Hello extends Component {
  render() {
    return <h2>Hello {this.props.name}!</h2>;
  }
}
//設置組件的props對象的類型信息
Hello.propTypes = {
  //要求name爲string類型
  name: PropTypes.string,
};

export default Hello;

調用:

import ReactDOM from "react-dom/client";
import React from "react";
import Hello from "./Hello";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let vNode = <Hello name={99} />;

//3、將Vnode渲染到根結點上
root.render(vNode);

PropTypes提供了許多驗證工具,用來幫助你確定props數據的有效性。在上面這個例子中,我們使用了PropTypes.stirng。意思是:name的值類型應該是string。 當Component的props接收到一個無效的值時,瀏覽器控制檯就會輸出一個警告。因此,<Hello name={99}/> 控制檯會出現如下警告:

處於性能原因,類型檢查僅在開發模式下進行。

2.6.2.PropTypes的更多驗證器

import React from 'react';
import PropTypes from 'prop-types';

class MyComponent extends React.Component {
  render() {
    // 利用屬性做更多得事
   }
}

MyComponent.propTypes = {
//你可以定義一個屬性是特定的JS類型(Array,Boolean,Function,Number,Object,String,Symbol)。默認情況下,這些都是可選的。

optionalArray: PropTypes.array,
optionalBool: PropTypes.bool,
optionalFunc: PropTypes.func,
optionalNumber: PropTypes.number,
optionalObject: PropTypes.object,
optionalString: PropTypes.string,
optionalSymbol: PropTypes.symbol,

//指定類型爲:任何可以被渲染的元素,包括數字,字符串,react 元素,數組,fragment。
optionalNode: PropTypes.node,

// 指定類型爲:一個react 元素
optionalElement: PropTypes.element,

//你可以類型爲某個類的實例,這裏使用JS的instanceOf操作符實現
optionalMessage: PropTypes.instanceOf(Message),


//指定枚舉類型:你可以把屬性限制在某些特定值之內
optionalEnum: PropTypes.oneOf(['News', 'Photos']),

// 指定多個類型:你也可以把屬性類型限制在某些指定的類型範圍內
optionalUnion: PropTypes.oneOfType([
  PropTypes.string,
  PropTypes.number,
  PropTypes.instanceOf(Message)
]),

// 指定某個類型的數組
optionalArrayOf: PropTypes.arrayOf(PropTypes.number),

// 指定類型爲對象,且對象屬性值是特定的類型
optionalObjectOf: PropTypes.objectOf(PropTypes.number),


//指定類型爲對象,且可以規定哪些屬性必須有,哪些屬性可以沒有
optionalObjectWithShape: PropTypes.shape({
  optionalProperty: PropTypes.string,
  requiredProperty: PropTypes.number.isRequired
}),

// 指定類型爲對象,且可以指定對象的哪些屬性必須有,哪些屬性可以沒有。如果出現沒有定義的屬性,會出現警告。
//下面的代碼optionalObjectWithStrictShape的屬性值爲對象,但是對象的屬性最多有兩個,optionalProperty 和 requiredProperty。
//出現第三個屬性,控制檯出現警告。
optionalObjectWithStrictShape: PropTypes.exact({
  optionalProperty: PropTypes.string,
  requiredProperty: PropTypes.number.isRequired
}),

//加上isReqired限制,可以指定某個屬性必須提供,如果沒有出現警告。
requiredFunc: PropTypes.func.isRequired,
requiredAny: PropTypes.any.isRequired,

// 你也可以指定一個自定義的驗證器。如果驗證不通過,它應該返回Error對象,而不是`console.warn `或拋出錯誤。`oneOfType`中不起作用。
customProp: function(props, propName, componentName) {
  if (!/matchme/.test(props[propName])) {
    return new Error(
      'Invalid prop `' + propName + '` supplied to' +
      ' `' + componentName + '`. Validation failed.'
    );
  }
},

//你也可以提供一個自定義的驗證器 arrayOf和objectOf。如果驗證失敗,它應該返回一個Error對象。
//驗證器用來驗證數組或對象的每個值。驗證器的前兩個參數是數組或對象本身,還有對應的key。
customArrayProp: PropTypes.arrayOf(function(propValue, key,     componentName, location, propFullName) {
  if (!/matchme/.test(propValue[key])) {
    return new Error(
      'Invalid prop `' + propFullName + '` supplied to' +
      ' `' + componentName + '`. Validation failed.'
    );
  }
})

示例:

Hello.js

import React, { Component } from "react";
import PropTypes from "prop-types";

class Hello extends Component {
  render() {
    return <h2>Hello {this.props.name}!</h2>;
  }
}
//設置組件的props對象的類型信息
Hello.propTypes = {
  //要求name爲string類型
  name: PropTypes.string,
  sex: PropTypes.oneOf(["男", "女"]),
  //props 屬性對象 ,propName 當前屬性名,componentName組件名
  phone: function (props, propName, componentName) {
    if (!/\d{13}/.test(props[propName])) {
      return new Error("手機號碼必須是13位的數字");
    }
  },
};

export default Hello;

index.js

import ReactDOM from "react-dom/client";
import React from "react";
import PostList from "./PostList";
import Hello from "./Hello";

//1、創建根節點
let root = ReactDOM.createRoot(document.getElementById("root"));

let vNode = <Hello name={99} sex="未知" phone="abc" />;

//3、將Vnode渲染到根結點上
root.render(vNode);

結果:

2.6.3. 子元素

        static propTypes={

                屬性:PropTypes.類型.(是否必填,不能爲空)

        }
  //city必須是string類型且必須填寫
  city: PropTypes.string.isRequired,

2.6.4. 子元素

限制單個子元素

使用 PropTypes.element 你可以指定只有一個子元素可以被傳遞給組件。

 //將children限制爲單個子元素。
Greeting.propTypes = {
  name: PropTypes.string,
  children: PropTypes.element.isRequired
};

如果如下方式引用Greeting組件:

//傳了兩個子元素給組件Greeting那麼,控制檯會出現警告

 //傳了兩個子元素給組件Greeting那麼,控制檯會出現警告
<Greeting name={'world'}>
      <span>子元素1</span>
      <span>子元素2</span>
 </Greeting>

警告如圖:

限制其它子元素

children: PropTypes.number,

獲取子元素:

使用children

class Hello extends Component {
  render() {
    return (
      <h2>
        Hello {this.props.name}! {this.props.children}
      </h2>
    );
  }
}

使用屬性

// index.tsx

import React from 'react'
import NavBar from './NavBar'

const ReactSlot = () => {
  return (
    <div>
      <NavBar 
        leftSlot={<div>left---這裏內容可以隨意填充</div>}
        centerSlot={<div>center---這裏內容可以隨意填充</div>}
        rightSlot={<div>right---這裏內容可以隨意填充</div>}
      ></NavBar>
    </div>
  )
}

export default ReactSlot
// NavBar.tsx

import React, { ReactNode } from 'react'
import './navbar.css'

type Props = {
  leftSlot: ReactNode
  centerSlot: ReactNode
  rightSlot: ReactNode
}
const NavBar = (props:Props) => {
  return (
    <div className='navbar-container'>
      <div className='navbar-left'>
        {props.leftSlot}
      </div>
      <div className='navbar-center'>
        {props.centerSlot}
      </div>
      <div className='navbar-right'>
        {props.rightSlot}
      </div>
    </div>
  )
}

export default NavBar

2.6.5.指定默認屬性值

你可以給組件分配一個特殊的defaultProps屬性。

//給Greeting屬性中的name值指定默認值。當組件引用的時候,沒有傳入name屬性時,會使用默認值。
Greeting.defaultProps = {
  name: 'Stranger'
};

// ES6可以這樣寫

class Greeting extends React.Component {
  static defaultProps = {
    name: 'stranger'
  }
  render() {
    return (
      <div>Hello, {this.props.name}</div>
    )
  }
}

Hello.js

import React, { Component } from "react";
import PropTypes from "prop-types";

class Hello extends Component {
  render() {
    return (
      <h2>
        Hello {this.props.name}! {this.props.children} {this.props.city}
      </h2>
    );
  }
}
//設置組件的props對象的類型信息
Hello.propTypes = {
  //要求name爲string類型
  name: PropTypes.string,
  sex: PropTypes.oneOf(["男", "女"]),
  //props 屬性對象 ,propName 當前屬性名,componentName組件名
  phone: function (props, propName, componentName) {
    if (!/\d{13}/.test(props[propName])) {
      return new Error("手機號碼必須是13位的數字");
    }
  },
  children: PropTypes.number,
  //city必須是string類型且必須填寫
  city: PropTypes.string.isRequired,
};

Hello.defaultProps = {
  city: "珠海",
};

export default Hello;

如果有默認值就變成了非必填了。

 2.7、React-組件樣式的兩種方式

與傳統使用CSS給HTML添加樣式的方式相比,React在給元素添加樣式的時候方式有所不同。React的核心哲學之一就是讓可視化的組件自包含,並且可複用。這就是爲什麼HTML元素和Javascript放在一起組成了(組件)。本節內容我們將介紹React定義樣式的方式。

2.7.1、第一種方式:選擇器樣式

首先創建Person.css,定義我們所需要的樣式,具體樣式代碼定義爲如下形式:

.Person{width:60%;margin:16pxauto;border:1pxsolid#eee;box-shadow:2px3px#ccc;padding:16px;text-align:center;}

input{width:200px;border:1pxsolid#eee;outline:none;border-radius:10px;padding:16px;}

如果要使用樣式,需要在Person.js組件中引入:

import'./Person.css'

這時可以查看頁面變化了。

2.7.2、第二種方式:內聯樣式

React推崇的是內聯的方式定義樣式。這樣做的目的就在於讓你的組件更加的容易複用。下面給按鈕添加一個內聯樣式:

來到App.js文件,將button按鈕定義爲如下形式:

style={{backgroundColor:'white',border:'1px solid blue',padding:'8px',cursor:'pointer'}}>

更改狀態值

需要注意的是,JSX中使用樣式對象定義內聯樣式,複合樣式使用駝峯命名法,對象屬性直接使用逗號隔開。

// 讓數組中的每一項變成雙倍
const numbers = [2,2,4,5];
const doubles = numbers.map((item,index) => {
  return <li style={{ color: 'red', fontWeight: 200 }} key={index}> {item * 2}}</li>
})

使用圖片

import React from "react";

import like from "./assets/images/like.png";

function PostItem(props) {
  const { post, onVote } = props;
  const handleVote = () => {
    onVote(post.id);
  };

  return (
    <li>
      <div>{post.title}</div>
      <div>
        創建人:<span>{post.author}</span>
      </div>
      <div>
        創建時間:<span>{post.date}</span>
      </div>
      <div>
        <span>
          <img src={like} onClick={handleVote} />
        </span>
        <span>{post.vote}</span>
      </div>
    </li>
  );
}

export default PostItem;

注意圖片不要放到src目錄以外。

 

2.8、組件的生命週期

其實React組件並不是真正的DOM, 而是會生成JS對象的虛擬DOM, 虛擬DOM會經歷創建,更新,刪除的過程

這一個完整的過程就構成了組件的生命週期,React提供了鉤子函數讓我們可以在組件生命週期的不同階段添加操作

在 React v17版本刪除componentWillMount()componentWillReceiveProps()componentWillUpdate() 這三個函數,保留使用 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()

這張圖是從 react生命週期鏈接裏找的,裏面有可以根據react不同版本查看對應的生命週期函數。

2.8.1、生命週期總覽

常用的:

 

 不常用的:

 

 

react的生命週期大概分爲

  • 組件裝載(Mount)組件第一次渲染到Dom樹
  • 組件更新(update)組件state,props變化引發的重新渲染
  • 組件卸載(Unmount)組件從Dom樹刪除

2.8.2、構造方法 constructor 

說明:

  1. 如果不需要初始化state,或者不進行方法的綁定,則不需要實現constructor構造函數
  2. 在組件掛載前調用構造函數,如果繼承React.Component,則必須調用super(props)
  3. constructor通常用於處理了state初始化和爲事件處理函數綁定this實例
  4. 儘量避免將props外部數據賦值給組件內部state狀態

注意: constructor 構造函數只在初始化化的時候執行一次

示例代碼如下:

import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0,
    };
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(this);
    this.setState({ count: this.state.count + 1 });
  }

  render() {
    return (
      <div>
        <h2>計算器</h2>
        <button onClick={this.handleClick}>點擊加1</button>
        <span> {this.state.count} </span>
      </div>
    );
  }
}

export default Counter;

運行結果:

組件state也可以不定義在constructor構造函數中,事件函數也可以通過箭頭函數處理this問題

因此如果不想使用constructor 也可以將兩者移出

示例代碼如下

import React, { Component } from "react";

class Counter extends Component {
  //初始化組件狀態
  state = {
    count: 0,
  };

  //箭頭函數處理this問題
  handleClick = () => {
    console.log(this);
    this.setState(() => ({ count: this.state.count + 1 }));
  };

  render() {
    return (
      <div>
        <h2>計算器</h2>
        <button onClick={this.handleClick}>點擊加1</button>
        <span> {this.state.count} </span>
      </div>
    );
  }
}

export default Counter;

輸出結果:

 

 

 箭頭函數中的this指向了當前組件實例,而普通函數則指向undefined。箭頭函數this是詞法作用域,由上下文確定。

2.8.3.組件裝載(Mount)

  • constructor: 在此初始化state,綁定成員函數this環境,props本地化,constructor 構造函數只在初始化化的時候執行一次
  • getDerivedStateFromProps:在創建時或更新時的render之前執行,讓 props 能更新到組件內部 state中,必須是靜態的。它應返回一個對象來更新 state,如果返回 null 則不更新任何內容。
  • render: 渲染函數,唯一的一定不能省略的函數,必須有返回值,返回null或false表示不渲染任何DOM元素。它是一個僅僅用於渲染的純函數,返回值完全取決於this.state和this.props,不能在函數中任何修改props、state、拉取數據等具有副作用的操作。render函數返回的是JSX的對象,該函數並不因爲這渲染到DOM樹,何時進行真正的渲染是有React庫決定的。
  • componentDidMount: 掛載成功函數。該函數不會再render函數調用完成之後立即調用,因爲render函數僅僅是返回了JSX的對象,並沒有立即掛載到DOM樹上,而componentDidMount是在組件被渲染到DOM樹之後被調用的。另外,componentDidMount函數在進行服務器端渲染時不會被調用。
import React, { Component } from "react";

class Hi extends Component {
  constructor(props) {
    super(props);
    console.log("constructor()函數被調用,構造函數");
    this.state = {
      n: 100,
    };
  }
  render() {
    console.log("render()函數被調用,返回UI描述");
    return <h2>Hello {this.props.name}!</h2>;
  }

  static getDerivedStateFromProps() {
    console.log(
      "getDerivedStateFromProps()函數被調用,讓 props 能更新到組件內部 state中"
    );
    return null;
  }

  componentDidMount() {
    console.log("componentDidMount()函數被調用,組件被掛載到DOM後");
  }
}

export default Hi;

運行結果:

修改案例,查看this

import React, { Component } from "react";

class Hi extends Component {
  constructor(props) {
    super(props);
    console.log("constructor()函數被調用,構造函數", this);
    this.state = {
      n: 100,
    };
  }
  render() {
    console.log("render()函數被調用,返回UI描述", this);
    return <h2>Hello {this.props.name}!</h2>;
  }

  static getDerivedStateFromProps() {
    console.log(
      "getDerivedStateFromProps()函數被調用,讓 props 能更新到組件內部 state中",
      this
    );
    return null;
  }

  componentDidMount() {
    console.log("componentDidMount()函數被調用,組件被掛載到DOM後", this);
  }
}

export default Hi;
View Code

2.8.4、組件更新(update)

當組件掛載到DOM樹上之後,props/state被修改會導致組件進行更新操作。更新過程會以此調用如下的生命週期函數:

  • shouldComponentUpdate(nextProps, nextState):是否重新渲染組件 返回bool值,true表示要更新,false表示不更新,使用得當將大大提高React組件的性能,避免不需要的渲染。
  • getSnapshotBeforeUpdate:在render之後,React更新dom之前執行。它使您的組件可以在可能更改之前從DOM捕獲一些信息(例如滾動位置),例如在聊天氣泡頁中用來計算滾動高度。它返回的任何值都將作爲參數傳遞給componentDidUpdate()
  • render: 渲染函數
  • componentDidUpdate: 更新完成函數

2.8.5、shouldComponentUpdate函數

說明:

  1. shouldComponentUpdate函數使用來判讀是否更新渲染組件
  2. 函數返回值是一個布爾值,爲true,表示繼續走其他的生命週期函數,更新渲染組件
  3. 如果返回一個false表示,不在繼續向下執行其他的生命週期函數,到此終止,不更新組件渲染
  4. 函數接受兩個參數, 第一個參數爲props將要更新的數據, 第二個參數爲state將要更新的數據
import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    console.log("constructor()構造函數", this);
  }

  //初始化組件狀態
  state = {
    count: 0,
  };

  //箭頭函數處理this問題
  handleClick = () => {
    console.log("調用this.setState更新狀態", this);
    this.setState(() => ({ count: this.state.count + 1 }));
  };

  render() {
    console.log("render()渲染UI", this);
    return (
      <div>
        <h2>計算器</h2>
        <button onClick={this.handleClick}>點擊加1</button>
        <span> {this.state.count} </span>
      </div>
    );
  }

  static getDerivedStateFromProps() {
    console.log(
      "getDerivedStateFromProps()函數被調用,讓 props 能更新到組件內部 state中",
      this
    );
    return null;
  }

  //是否允許組件更新
  shouldComponentUpdate(props, state) {
    console.log("shouldComponentUpdate()函數被調用,", props, state, this);
    return true;
  }

  componentDidMount() {
    console.log("componentDidMount()掛載成功", this);
  }
}

export default Counter;

結果

 

2.8.6、componentDidUpdate函數

說明:

  1. 組件render執行後,頁面渲染完畢了以後執行的函數
  2. 接受兩個參數,分別爲更新前的props和state
import React, { Component } from "react";

class Counter extends Component {
  constructor(props) {
    super(props);
    console.log("constructor()構造函數", this);
  }

  //初始化組件狀態
  state = {
    count: 0,
  };

  //箭頭函數處理this問題
  handleClick = () => {
    console.log("調用this.setState更新狀態", this);
    this.setState(() => ({ count: this.state.count + 1 }));
  };

  render() {
    console.log("render()渲染UI", this);
    return (
      <div>
        <h2>計算器</h2>
        <button onClick={this.handleClick}>點擊加1</button>
        <span> {this.state.count} </span>
      </div>
    );
  }

  static getDerivedStateFromProps() {
    console.log(
      "getDerivedStateFromProps()函數被調用,讓 props 能更新到組件內部 state中",
      this
    );
    return null;
  }

  //是否允許組件更新
  shouldComponentUpdate(props, state) {
    console.log(
      "shouldComponentUpdate()函數被調用,是否允許組件更新",
      props,
      state,
      this
    );
    return true;
  }

  //組件更新後
  componentDidUpdate(prevProps, prevState) {
    console.log(
      "componentDidUpdate()被調用,組件更新後",
      prevProps,
      prevState,
      this
    );
  }

  componentDidMount() {
    console.log("componentDidMount()掛載成功", this);
  }
}

export default Counter;

結果

 注意state是更新前的狀態。

2.8.7、React.createRef() 非生命週期函數

可以通過React.createRef()創建Refs並通過ref屬性聯繫到React組件。Refs通常當組件被創建時被分配給實例變量,這樣它們就能在組件中被引用。

import React, { Component } from "react";

class RefDemoCom extends Component {
  constructor(props) {
    super(props);
    this.txtName = React.createRef();
  }

  clickHandler = () => {
    console.log(this.txtName);
    this.txtName.current.focus();
    this.txtName.current.value = new Date().toLocaleString();
    this.txtName.current.style.color = "blue";
  };

  render() {
    return (
      <div>
        <h2>React.createRef()</h2>
        <input type="text" ref={this.txtName} />
        <button onClick={this.clickHandler}>點擊獲得按鈕</button>
      </div>
    );
  }
}

export default RefDemoCom;

 

2.8.8、getSnapshotBeforeUpdate函數

說明:

  1. getSnapshotBeforeUpdate必須跟componentDidUpdate一起使用,否則就報錯
  2. 但是不能與componentWillReceivePropscomponentWillUpdate一起使用
  3. state和props更新都會觸發這個函數的執行, 在render函數之後執行
  4. 接受兩個參數,更新前的props和當前的state
  5. 函數必須return 返回結果
  6. getSnapshotBeforeUpdate返回的結果將作爲參數傳遞給componentDidUpdate
示例:
import React, { Component } from "react";

class BeforeUpdateDemo extends Component {
  constructor(props) {
    super(props);
    this.state = { name: "tom" };
  }

  componentDidMount() {
    setTimeout(() => {
      this.setState({ name: "jack" });
    }, 1000);
  }

  getSnapshotBeforeUpdate(prevProps, prevState) {
    console.log("更新前的狀態:", prevState);
    document.getElementById("prev").innerHTML = "更新前:" + prevState.name;
    return document.getElementById("container").clientHeight;
  }

  componentDidUpdate(prevProps, prevState, preHeight) {
    console.log("更新後的狀態:", this.state);
    document.getElementById("next").innerHTML = "更新後:" + this.state.name;
    const height = document.getElementById("container").clientHeight;
    console.log("原高度:" + preHeight);
    console.log("現高度:" + height);
  }

  render() {
    return (
      <div id="container">
        <h2>示例</h2>
        <div id="prev"></div>
        <div id="next"></div>
      </div>
    );
  }
}

export default BeforeUpdateDemo;

運行結果:

2.8.9.組件卸載(Unmount)

  • componentWillUnmount:當React組件要從DOM樹上刪除前,會調用一次這個函數。這個函數經常用於去除componentDidMount函數帶來的副作用,例如清除計時器清除事件監聽

示例:

componentWillUnmount() 會在組件卸載及銷燬之前直接調用。在此方法中執行必要的清理操作,例如,清除 timer,取消網絡請求或清除在 componentDidMount() 中創建的訂閱等。

componentWillUnmount() 中不應調用 setState(),因爲該組件將永遠不會重新渲染。組件實例卸載後,將永遠不會再掛載它。

import React, { Component } from "react";

class HelloComponent extends Component {
  componentWillUnmount() {
    console.log("componentWillUnmount()被調用了!");
  }
  render() {
    return <div>HelloComponent被加載了</div>;
  }
}

class WillUnmountDemo extends Component {
  state = {
    i: 0,
  };

  handleClick = () => {
    this.setState({
      i: ++this.state.i,
    });
  };

  render() {
    return (
      <div>
        <h2>計算器</h2>
        <button onClick={this.handleClick}>i的當前值:{this.state.i}</button>
        {this.state.i % 2 === 0 ? (
          <HelloComponent />
        ) : (
          <h2>HelloComponent未加載</h2>
        )}
      </div>
    );
  }
}

export default WillUnmountDemo;

結果:

2.8.10、React V16.3 新增的生命週期方法

React v16.3雖然是一個小版本升級,但是卻對React組件生命週期函數有巨大變化。

新增生命週期如下:

  • getDerivedStateFromProps
  • getSnapshotBeforeUpdate

同時逐漸廢棄了以下生命週期:

  • componentWillReceiveProps
  • componentWillMount
  • componentWillUpdate

17 版本刪除了 componentWillMount()componentWillReceiveProps()componentWillUpdate() 這三個函數,保留使用 UNSAFE_componentWillMount()UNSAFE_componentWillReceiveProps()UNSAFE_componentWillUpdate()

2.8.11、不能使用setState的生命週期

2.8.12、小結

三、作業

3.1、定義一個組件,當文本框中輸入內容時在文本框後顯示輸入的值,雙向綁定。

 

 

3.2、請完成課程中的所有示例。

3.3、請定義一個vue分頁組件,可以實現客戶端分頁功能,接收參數

3.4、定義一個對話框組件,要求顯示在body標籤內

3.5、定義一個選項卡組件,3個不同的組件,點擊卡片名稱時動態切換。

3.6、完成如下示例

舉例:舉個在實際項目中使用此生命週期的例子,在聊天時的氣泡頁會遇到彈新的消息氣泡場景,此時組件重新渲染了,如果不重新給外層包裹的dom計算滾動高度,會導致dom不停下滑。

在線demo: getSnapshotBeforeUpdate例子

動圖封面

而使用getSnapshotBeforeUpdate可以優化此場景。在getSnapshotBeforeUpdate生命週期中記錄外層dom元素的高度perScrollHeight,在componentDidUpdate中重新計算外層dom的scrollTop。此時,頁面滾動一段距離不會出現消息跳動的現象 優化後的例子

this.listRef.current.scrollTop = curScrollTop + (this.listRef.current.scrollHeight - perScrollHeight)

代碼: github代碼

3.7、定義一個子組件,每隔1秒數字加1,在父組件中定義一個按鈕進行顯示隱藏子組件,隱藏子組件時要求停止計數,點擊顯示時從0開始重新計數。

四、視頻

 

五、源碼

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章