React 0基礎學習路線(3)— 詳述組件基礎及props和state原理(附詳細案例代碼解析過程)

1. 重點提煉

1. 1 組件

  • 函數式組件

    • 可以通過函數的第一個參數 props來接收數據
    • 函數到返回值就是組件的視圖
  • 類式組件

    • 通過 class類的定義
    • 需要繼承自 React.Component
    • 可以通過類的 props屬性來接收傳入的數據
    • 類組件擁有:state,組件內部的私有狀態(數據)
    • 通過類的 render返回組件的視圖
  • state

    • 類的內部私有狀態(數據)
    • 需要在類的構造函數中進行初始化
      • 如果一個組件類重寫類構造函數,那麼就需要手動的調用 super,並傳入 props
    • 如果需要對組件的 state 進行修改的時候,需要調用組件對象的 setState 方法
    • setState接收的一個對象或者函數
      • 如果是對象的,那麼這個對象會被合併到組件的 state
      • setState 是異步
      • setState 是更新合併的(多次調用會合並)
      • 還可以通過傳入回調函數的形式對state進行修改
    • setState 調用的時候,會調用 render 方法,對視圖進行重新渲染
  • 事件

    • 通過行間 on事件名稱的形式來綁定的
    • 事件名稱是駝峯命名規則:onMouseOver
    • 事件綁定的函數
      • this 默認爲 undefined
      • 事件函數的第一個參數默認爲:event對象
        • 我們可以在事件函數通過event 對象來訪問具體的 dom 元素,還可以阻止默認行爲、阻止冒泡等
      • 我們可以通過 bind方法來綁定this,然後再賦值給事件
  • stateprops

  • props 是外部傳入的數據

    • 組件內部只能使用,而不能直接修改

    • state 是組件內部私有數據

      • 組件內部可以訪問,也可以通過 setState 進行修改
      • 組件的外部不能訪問某個組件內部的state
  • 如果需要修改props

    • 由外部使用該組件的父級通過 props 傳入一個函數

    • 子級通過 this.props函數 去調用,並傳入想要修改的數據,通知父級進行修改(僞代碼演示)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script>
 
    let user = {username: 'zs', age: 35};
 

    // 爲什麼不推薦子級直接修改父級參數值?因爲假如別的組件也要用這個變量(作爲參數),就可能會出現問題!
    child( user );
    child2( user );
 
    console.log(user);
 
    function child(data) {
        // 類似自定義組件
        data.username = 'ls'; // 子級不能直接修改父級傳遞的參數,但是如果傳遞的是props是對象,子級是可以直接修改父級參數的,但是不推薦這樣做!
    }
 
    function child2(data) {
        // 這裏可能也用到了user
        console.log(data);
    }
//注意:數據不是誰想改就能改的,而是由所有者負責修改!這塊由父級決定,子級申請修改!
 
</script>
 
</body>
</html>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
 
<script>
 
    let user = {username: 'zs', age: 35};
 
    function onChange(v) {
        user.username = v;
    }
 
    //父級調用子級函數
    child( user, onChange );
    // 爲什麼不推薦子級直接修改父級參數值?因爲假如別的組件也要用這個變量(作爲參數),就可能會出現問題!
    child2( user );
 
    console.log(user);
 
    function child(data, onChange) {
        // 類似自定義組件
        // data.username = 'ls'; 子級不能直接修改父級傳遞的參數,但是如果傳遞的是props是對象,子級是可以直接修改父級參數的,但是不推薦這樣做!
 
        //  如果要修改父級傳遞參數,應該告知數據持有人。問他是否允許改,如果它同意則改!(通過接收回調函數作爲參數!)
        // 如果要修改則調用 onChange,onChange是由父級類定義的
        onChange('ls');
    }
 
    function child2(data) {
        // 這裏可能也用到了user
        console.log(data);
    }
//注意:數據不是誰想改就能改的,而是由所有者負責修改!這塊由父級決定,子級申請修改!
 
</script>
 
</body>
</html>

2. 腳手架構建

create-react-app app

  構建完,運行看看是否通過

npm start

image-20200701210756159

  刪除一些不需要的文件

image-20200701211841131

src/index.js 去掉一些不需要的引入。(註釋部分)

image-20200701211402572

src/App.js去掉一些不需要的引入。(註釋部分)

image-20200701211958549

2. 1 example01

app\src\App.js

import React from 'react';

function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
    </div>
  );
}

export default App;

image-20200601095822903

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.1

Branch:branch1

tag:v0.1

2. 2 組件

  web 組件(其實html就是組件,組件其實具有獨立功能的最小單元)就是對 web 中的數據、結構、方法等進行封裝,複用,與 JavaScript 中功能函數封裝類似,但更關注的是對 web 元素(標籤)的封裝與擴展(原生提供擴展:webComponent<自己可自定義標籤,進行擴展>,React就是出於這種思想而來)

  我們在js中通過函數的方式封裝一個選項卡,然後通過調用該函數,就可以在頁面中指定的位置顯示該選項卡,那麼在React中我們把這樣的一個函數稱爲組件(它具有一定獨立的功能),我們以html標籤的形式進行調用。實際上它就是自定義html標籤,可以自定義結構、樣式、功能。

* - 自定義html標籤
*      - 結構
*      - 樣式
*      - 功能

React 提供了兩種組件構建方式

  • 函數式組件
  • 類式組件

2. 2. 1 函數式組件

  在 React.js 中,定義一個組件的最簡單的方式就是 函數

function myComponent() {
    return (
        <div>
            <h2>我的組件</h2>
        </div>
    );
}
ReactDOM.render(
    <myComponent />,
    document.getElementById('app')
);
  • 函數的名稱就是組件的名稱
  • 函數的返回值就是組件要渲染的內容

函數的名稱(組件的名稱),必須是首字母大寫

  注意JSX的解析要依賴於React,如果沒加此依賴,可能會報錯!即import React from 'react';

2. 2. 1. 1 example02

App.js

import React from 'react';
 
import Custom from './components/custom';
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {Custom()}
    </div>
  );
}
 
export default App;

src/components/custom.js

import React from 'react';
 
export default function Custom() {
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601104657228

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.11

Branch:branch1

tag:v0.11

  但是這裏我們當作函數去用,還不算太好。React爲我們提供了更加方便的方式,如果函數調用返回函數式組件(JSX),我們可以像標籤一樣去使用這個函數,它會自動解析。

function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      <Custom />
    </div>
  );
}

image-20200601104657228

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.12

Branch:branch1

tag:v0.12

* 函數組件
*  - 通過函數來封裝一個組件,函數的返回值將作爲該組件渲染的結構

注意:

* 自定義的組件與html原生組件(標籤)
*  - 原生的標籤使用 小寫
*  - 自定義的組件名稱必須式大寫

2. 2. 2 類式組件

  我們還可以通過 類(class)類定義組件

* 類組件
*  - 通過類來封裝一個組件,該類必須繼承 React.Component 的基類
*  - 類中必須包含一個 render 方法
class Component01 extends React.Component {
    render() {
        return (
            <div>
                <h2>我的組件!</h2>
            </div>
        );
    }
}
  • 組件類必須繼承 React.Component
  • 組件類必須有 render 方法
  • render 方法的返回值就是組件要渲染的內容

類的名稱(組件的名稱),也必須是首字母大寫

2. 2. 2. 1 example03

src/components/custom02.js

import React from 'react';
// 繼承React.Component類
export default class Custom02 extends React.Component {
 
    render() {
        return (
            <div>
                <h2>React.Component -!</h2>
            </div>
        );
    }
 
}

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      {/*<Custom />*/}
      <Custom02 />
    </div>
  );
}
 
export default App;
image-20200601110313338

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.13

Branch:branch1

tag:v0.13

2. 3 組件案例

example-friendList

樣式

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    display: none;
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

2. 3. 1 創建 FriendList 組件

class FriendList extends React.Component {
      render() {
          return (
              <div className="friend-list">
                <div className="friend-group">
                    <dt>家人</dt>
                    <dd>爸爸</dd>
                    <dd>媽媽</dd>
                </div>
                <div className="friend-group">
                    <dt>朋友</dt>
                    <dd>張三</dd>
                    <dd>李四</dd>
                    <dd>王五</dd>
                </div>
                <div className="friend-group">
                    <dt>客戶</dt>
                    <dd>阿里</dd>
                    <dd>騰訊</dd>
                    <dd>頭條</dd>
                </div>
            </div>
        );
    }
}

2. 3. 1. 1 example04

src/components/friendList/index.css

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group {
    margin: 0;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    /*先註釋display樣式*/
    display: -none;  
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
    render() {
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    <div className="friend-group">
                        <dt>家人</dt>
                        <dd>爸爸</dd>
                        <dd>媽媽</dd>
                    </div>
                    <div className="friend-group">
                        <dt>朋友</dt>
                        <dd>張三</dd>
                        <dd>李四</dd>
                        <dd>王五</dd>
                    </div>
                    <div className="friend-group">
                        <dt>客戶</dt>
                        <dd>阿里</dd>
                        <dd>騰訊</dd>
                        <dd>頭條</dd>
                    </div>
                </div>
            </div>
        )
    }
 
}

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默認加載可以省略
 
import FriendList from "./components/friendList";
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom()}*/}
      {/*<Custom />*/}
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

  這裏在頁面中加入了一個好友面板組件,面板分爲三個組,每個組有不同的數據。我們開發的時候要形成組件化思想(每個組件看出獨立化的單元),這裏的好友面板是一個組件,每一組又是一個獨立單元。我們再App.js裏,我們是把好友面板組件當成一個獨立單元去應用。當我們進入好友面板組件的index.js,再把每個分組看成一個獨立的單元,可再細分組件。

image-20200601133757091

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.14

Branch:branch1

commit description:v0.14-example04

tag:v0.14

2. 3. 2 組件複用 - 數據抽取

  爲了提高組件的複用性,通常會把組件中的一些可變數據提取出來

let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '媽媽'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '張三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客戶',
        list: [
            {name: '阿里'},
            {name: '騰訊'},
            {name: '頭條'}
        ]
    }
};

2. 3. 2. 1 example05

  需求:動態數據渲染分組

  首先得把數據準備好,由於數據數量少,我們暫時定義在。

  組件封裝完畢後需要抽取,如果想要複用,則需要把組件中可變的部分抽取出來,作爲參數傳遞。

  (類似)函數調用:將數據作爲參數傳遞

app\src\App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默認加載可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '媽媽'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '張三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客戶',
        list: [
            {name: '阿里'},
            {name: '騰訊'},
            {name: '頭條'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {Custom(datas)}
      {/*<Custom />*/}
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601134820480

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.15

Branch:branch1

commit description:v0.15-example05

tag:v0.15

2. 3. 3 組件複用 - 數據傳入

  數據雖然分離了,但是爲了降低組件與數據之前的依賴,我們應該儘量避免在組件內部直接訪問數據,通過傳參的方法來進行解耦。

2. 3. 3. 1 example06

  以後開發過程中要形成組件化思想,即自定義標籤構建,而不是函數調用了。

  需求:通過標籤傳參,借用標籤屬性

src/App.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默認加載可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '媽媽'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '張三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客戶',
        list: [
            {name: '阿里'},
            {name: '騰訊'},
            {name: '頭條'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom(datas)}*/}
      <Custom datas={datas}/>
      {/*<Custom02 />*/}
      <FriendList />
    </div>
  );
}
 
export default App;

  接收到的是一個對象,對象裏包着datas

image-20200601135125683

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.16

Branch:branch1

commit description:v0.16-example06

tag:v0.16

2. 3. 3. 2 example07

  通常我們把參數設置爲props,傳遞的參數在props.datas底下:

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props.datas);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601135425103

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.17

Branch:branch1

commit description:v0.17-example07

tag:v0.17

2. 3. 3. 3 example08

  如果想傳遞多個參數:

src/App.js

function App() {
    return (
        <div className="App">
            <h1>CSDN</h1>
            <hr />
            {/*{Custom()}*/}
            {<Custom datas={datas} v={1} k={'kkk'} />}
            {/*<Custom02 />*/}
 
            <FriendList />
        </div>
    );
}

src/components/custom.js

import React from 'react';
 
export default function Custom(props) {
    console.log(props);
    return (
        <div>
            <h2>CSDN</h2>
            <div>
            </div>
        </div>
    );
}

image-20200601135644448

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.18

Branch:branch1

commit description:v0.18-example08

tag:v0.18

2. 3. 3. 4 property

  使用組件的時候通過 標籤屬性-property 的方式傳入數據,在組件內部通過構造函數參數(如果是函數組件,則通過函數參數)來接收傳入的數據

<組件名稱 屬性名稱="值" />
// 使用表達式
<組件名稱 屬性名稱={表達式} />
ReactDOM.render(
    <FriendList datas={datas} />,
    document.getElementById('app')
);
2. 3. 3. 4. 1 example09

  以上就是函數式組件傳參的方式,和函數調用區別不大。

  如果是一個類組件,其實差不多!

/**
* 在類組件中,通過標籤屬性傳入的數據是保存到類中一個屬性:props
*/

src/index.js

import React from 'react';
 
import Custom from './components/custom';
import Custom02 from './components/custom02';
 
//index.js默認加載可以省略
 
import FriendList from "./components/friendList";
 
let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '媽媽'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '張三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客戶',
        list: [
            {name: '阿里'},
            {name: '騰訊'},
            {name: '頭條'}
        ]
    }
};
 
function App() {
  return (
    <div className="App">
      <h1>CSDN</h1>
      <hr />
      {/*{Custom(datas)}*/}
      {/*<Custom datas={datas} v={1} k={'kkk'}/>*/}
      {/*<Custom02 />*/}
      <FriendList datas={datas}/>
    </div>
  );
}
 
export default App;

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        console.log(this.props);
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    <div className="friend-group">
                        <dt>家人</dt>
                        <dd>爸爸</dd>
                        <dd>媽媽</dd>
                    </div>
                    <div className="friend-group">
                        <dt>朋友</dt>
                        <dd>張三</dd>
                        <dd>李四</dd>
                        <dd>王五</dd>
                    </div>
                    <div className="friend-group">
                        <dt>客戶</dt>
                        <dd>阿里</dd>
                        <dd>騰訊</dd>
                        <dd>頭條</dd>
                    </div>
                </div>
            </div>
        )
    }
 
}

image-20200601141040872

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.19

Branch:branch1

commit description:v0.19-example09

tag:v0.19

2. 3. 3. 5 接收參數 - props

  在組件對應的函數或類中通過:

  • 函數式組件:通過函數的第一個參數來接收
  • 類式組件:通過類的構造函數第一個參數來接收

  無論是函數式組件還是類式組件,都會把傳入的參數組裝成一個對象:

<組件名稱 屬性名稱1="值1" 屬性名稱二={表達式二} />
 
// 函數式組件
function 組件名稱(參數) {
      // 參數的結構爲
      參數 = {
          屬性名稱1: "值1",
          屬性名稱二: 表達式二的值
    }
 
      return <div>組件結構</div>
}
// 類式組件
class 組件名稱 extends React.Component {
      constructor(參數) {
          super(參數);
 
          this.props = {
              屬性名稱1: "值1",
              屬性名稱2: 表達式二的值
        }
    }
 
      render() {
          return <div>組件結構</div>
    }
}

在類式組件中,需要注意:

  • 當子類重寫 constructor ,則必須調用父類 super - 類的知識點
  • 把接收到的參數傳入父類構造函數,父類構造函數中會創建一個對象屬性:props 類存儲傳入的參數數據

2. 3. 3. 6 通過參數動態渲染組件結構

class FriendList extends React.Component {
      constructor(props) {
          super(props);
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <div className="friend-group" key={key}>
                        <dt>{datas[key].title}</dt>
                                {datas[key].list.map((list, index) => <dd key={index}>{list}</dd>)}
                    </div>
                ))}
            </div>
        );
    }
}
2. 3. 3. 6. 1 example10

  可直接對參數解構,循環進行渲染,爲了讀起來暢快,我們把render中的下一級"friend-group"取出,封裝成函數組件,利用類組件和函數組件搭配應用。

src/components/friendList/index.js

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    renderList() {
        let {datas} = this.props;
 
        // 取出json對象中所有key生成數組再遍歷
        return Object.keys(datas).map( (key) => {
            let data = datas[key];
            return (
                <dl key={key} className="friend-group">
                    <dt>{data.title}</dt>
                </dl>
            )
        } )
    }
 
    render() {
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {this.renderList()}
                </div>
            </div>
        )
    }
 
}

image-20200601142158314

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.20

Branch:branch1

commit description:v0.20-example10

tag:v0.20

2. 3. 3. 7 子組件提取

  組件與類一樣,是一個不斷提取的過程,當我們發現某個部分可複用或者結構複雜的時候,我們可以對它再次進行提取

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
    }
 
      render() {
          let {data} = this.props;
          return (
                <div className="friend-group">
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
 
class FriendList extends React.Component {
      constructor(props) {
          super(props);
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <FriendGroup data={datas[key]} key={key} />
                ))}
            </div>
        );
    }
}
2. 3. 3. 7. 1 example11

  其實沒必要封裝renderList函數,可直接在函數中寫:

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        //解構數據
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let data = datas[key];
                                return (
                                    <dl key={key} className="friend-group">
                                        <dt>{data.title}</dt>
                                    </dl>
                                )
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601142158314

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.211

Branch:branch1

commit description:v0.211-example11-1

tag:v0.211

  完善後面內容:繼續循環json對象中的list,取值渲染組件。可再把data對象下的list數組取出,再循環取值渲染dd標籤。

import React from 'react';
import './index.css';
 
export default class FriendList extends React.Component {
 
    render() {
        //解構
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let data = datas[key];
                                let {list} = data;
                                return (
                                    <dl key={key} className="friend-group">
                                        <dt>{data.title}</dt>
                                        {
                                            list.map(item => {
                                                return <dd>{item.name}</dd>
                                            })
                                        }
                                    </dl>
                                )
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601143117870

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.212

Branch:branch1

commit description:v0.212-example11-2

tag:v0.212

2. 3. 3. 7. 2 example12

  其實組件傳參,就類似於函數傳參,可將外部數據傳入,組件內部通過this.props屬性拿到數據,然後內部根據數據進行渲染頁面。

  上面的render有點亂,組件和函數一樣,設計的時候儘量功能單一,把冗餘的功能拆出去,在這裏我們可以把第二層list循環拆出來封裝!

src/components/friendList/group.js

import React from 'react';
 
export default class Group extends React.Component {
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }
 
}

src/index.js

import React from 'react';
import './index.css';
import Group from './group'
 
export default class FriendList extends React.Component {
 
    render() {
        //解構數據
        let {datas} = this.props;
        console.log(datas);
 
        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                        {
                            Object.keys(datas).map( (key) => {
                                let group = datas[key];  // 帶了一個title,和list 的對象
                                return <Group key={key} data={group}></Group>
                            } )
                        }
                </div>
            </div>
        )
    }
 
}

image-20200601143117870

  如果後續group.jsrender中循環又很繁瑣,爲了使組件功能單一化,減少耦合,可繼續往後封裝組件。其實這和函數調用與封裝是非常類似的!但組件比函數更好用。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/V0.22

Branch:branch1

commit description:v0.22-example12

tag:v0.22

2. 3. 4 組件交互

  一個組件除了有結構、樣式,有的時候還會有交互,如上面的例子:現在我們希望當用戶點擊組件面板title的時候,面板組件的展開收縮狀態就會發生改變

2. 3. 4. 1 綁定事件

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
          this.state = {
              expanded: false
        };
          this.expand = this.click.expand();
    }
 
      expand() {
          this.setState({
              expanded: !this.state.expanded
        });
    }
 
      render() {
          let {data} = this.props;
          let {expanded} = this.state;
          return (
                <div className={[
                "friend-group",
                expanded && "expanded"
            ].join(' ')}>
                <dt onClick={this.expand}>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}

React.js 的事件綁定需要注意:

  • 事件名稱是駝峯命名的

  • 事件綁定函數的 this 指向

    • 通過 bind 改變 this 指向,爲了能夠在方法中調用組件對象的屬性和其它方法,我們需要把 this 指向組件

    • 通過箭頭函數處理 this

2. 3. 4. 2 獲取事件對象

  事件綁定函數的第一個參數是事件對象,通過事件對象可以獲取到原生 DOM 對象

2. 3. 4. 2. 1 example13

  需求:展開和收縮面板

  把樣式中的.friend-groupdisplay: none;註釋打開。

src/components/friendList/index.css

.friend-list {
    border: 1px solid #000000;
    width: 200px;
}
.friend-group {
    margin: 0;
}
.friend-group dt {
    padding: 10px;
    background-color: rgb(64, 158, 255);
    font-weight: bold;
}
.friend-group dd {
    padding: 10px;
    display: none;
}
.friend-group.expanded dd {
    display: block;
}
.friend-group dd.checked {
    background: green;
}

  我們點擊是需要增加事件的。

  注意:React的JSX的標籤中增加的事件,命名風格:onXxxx,採用on後面單詞小駝峯命名。

src/components/friendList/group.js

    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={()=>{console.log("你點到我了!")}}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )

  但是這樣寫肯定不好,如果函數邏輯複雜,這些寫會很噁心,我們可以把函數拿到外面去!

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-1

Branch:branch1

commit description:v0.23-1-example13-1 (展開和收縮面板:React的JSX的標籤中增加的事件)

tag:v0.23-1

src/components/friendList/group.js

import React from 'react';
 
export default class Group extends React.Component {
 
    toggle() {
        console.log('你點到我了嗎')
    }
 
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
 
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-2

Branch:branch1

commit description:v0.23-2-example13-2(展開和收縮面板:React的JSX的標籤中增加的事件,優化函數在標籤外部)

tag:v0.23-2

  我們在看一下事件中的this

    toggle() {
        console.log(this);
    }

  事件函數中this無非兩種情況:1、當前元素(dt標籤) 2、當前所對應的事件對象,但實際卻是undefined

  React默認情況下,事件綁定的函數中this值爲undefined,我們需要根據實際使用的情況來手動處理(可以在調用的時候,利用bind方法綁定this)。

  爲什麼是這樣呢?

  因爲這個方法可以被事件調用,也可以在類的方法中用this調用等,有很多種形式,所以React就直接默認this爲空從而避免發生奇怪問題,然後由開發人員自己確定。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-3

Branch:branch1

commit description:v0.23-3-example13-3(展開和收縮面板:查看React的JSX的標籤中事件函數this指向)

tag:v0.23-3

  可以在調用的時候,利用bind方法綁定this

  事件對象:事件函數的第一個參數就是事件對象(是被React處理過的對象,不是原生對象,原生在它的nativeEvent屬性中,但是使用方式和原生是一樣的。這個很像jq中的二次事件對象,即處理兼容後的事件對象)

image-20200702160523039

import React from 'react';
 
export default class Group extends React.Component {
    toggle(e) {
        console.log(this);
        console.log(e.target);
    }
 
    render() {
        let {title, list} = this.props.data;
        return(
            <dl className="friend-group">
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }
 
}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.23-4

Branch:branch1

commit description:v0.23-4-example13-4(展開和收縮面板:指定React的JSX的標籤中事件函數this指向,查看事件對象)

tag:v0.23-4

  這裏暫時未實現面板手收縮,我們放在下一個實例中。

2. 3. 4. 3 react組件中的事件小結

  • 事件是通過 jsx 標籤上的行間事件屬性來綁定函數的
    • 事件名稱是使用小駝峯命名:onMouseOver
    • 事件綁定函數可以直接寫在行間,也可以綁定到類中其他方法中
  • 事件中this
    • 默認情況下,事件綁定的函數中this值爲undefined,我們需要根據實際使用的情況來手動處理(可以在調用的時候,利用bind方法綁定this)
  • 事件對象
    • 事件函數的第一個參數就是事件對象(是被React處理過的對象,不是原生對象,原生在它的nativeEvent屬性中,但是使用方式和原生是一樣的。這個很像jq中的二次事件對象,即處理兼容後的事件對象)

2. 3. 5 組件狀態

  有的時候,一個組件除了可以接收外部傳入的數據,還會有屬於自己的內部私有數據,同時組件私有數據的更新還會導致組件的重新渲染,比如上面的例子中的每一個好友面板會有一個內部私有數據來控制面板的展開與收縮,我們又把這種數據稱爲組件狀態

2. 3. 5. 1 state

  存儲組件狀態的對象

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
          this.state = {
              expanded: false
        };
    }
 
      render() {
          let {data} = this.props;
          let {expanded} = this.state;
          return (
                <div className={[
                "friend-group",
                expanded && "expanded"
            ].join(' ')}>
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
2. 3. 5. 1. 1 example14

  有的時候,一個組件除了可以接收外部傳入的數據,還會有屬於自己的內部私有數據,同時組件私有數據的更新還會導致組件的重新渲染,比如上面的例子中的每一個好友面板會有一個內部私有數據來控制面板的展開與收縮,我們又把這種數據稱爲組件狀態

  因此組件有兩種狀態分爲外部狀態和內部狀態,一個外部傳入(props),一個是內部私有(state),兩者互不干擾。當然有的時候,可以將props對象的某個值作爲state的初始值去使用。

  state在父類React.Component就進行了定義,我們需要做的事情就是初始賦值就行了。state 的初始化工作需要在構造函數中進行,構造函數其實就是組件最開始執行的一個函數。

  注意:在構造函數中養成習慣,將類中方法綁定this指針。

  收縮功能實現,小迪寫的樣式中的.friend-group.expanded dd,如果className中存在則展開,否則收縮。我們可以採取數組形式,放多個className,最後會自動(調用toString)幫我們轉字符串。

app\src\components\friendList\group.js

import React from 'react';
 
export default class Group extends React.Component {
 
    toggle(e) {
        console.log(this);
        console.log(e.target);
    }
 
    render() {
        let {title, list} = this.props.data;
       
        return(
            <dl className={['friend-group','expanded']}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item => <dd key={item.name}>{item.name}</dd> )
                }
            </dl>
        )
 
    }
 
}

  類名多了逗號!

image-20200702163141489

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-1

Branch:branch1

commit description:v0.24-1-example14-1 (展開和收縮面板:樣式丟失問題)

tag:v0.24-1

  發現有逗號了,樣式才丟失,需要join採取空格分隔即可

import React from "react";

export default class Group extends React.Component {

    toggle(e) {
        console.log(this);
        console.log(e.target);
    }

    render() {
        let {title, list} = this.props.data;
        return (
            <dl className={['friend-group','expanded'].join(' ')}>
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

image-20200702163544232

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-2

Branch:branch1

commit description:v0.24-2-example14-2 (展開和收縮面板:解決樣式丟失問題)

tag:v0.24-2

  我們採用三目運算符,動態變化。

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展開收縮狀態,默認收縮
        }
        // 一般都在構造函數中綁定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.state.expanded = !this.state.expanded;
        console.log(this.state.expanded);
    }

    render() {
        let {title, list} = this.props.data;
        let {expanded} = this.state;
        
        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle.bind(this)}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  點擊有反應,但沒有任何效果,這是啥情況呢?

  實際上state 中的值不能直接去改變,react 中並沒有主動的去監聽攔截數據的變化,如果直接去修改state中的值是不會重新渲染視圖的。但它提供了方法:setState來修改state中的值,且在方法中去調用render。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.24-3

Branch:branch1

commit description:v0.24-3-example14-3 (展開和收縮面板:切換面板無響應!)

tag:v0.24-3

2. 3. 6 狀態更新

  當我們希望更新組件狀態的時候,不要直接修改 state 的值,而是需要調用 setState 方法來進行更新

2. 3. 6. 1 setState 方法

  setState 方法由父類 React.Component 提供,當該方法被調用的時候,組件的 state 會被更新,同時會重新調用組件的 render 方法對組件進行渲染

不要直接修改 state,而是調用 setState 方法來更新,組件構造函數是唯一可以對 state 直接賦值(初始化)的位置

this.setState({key:val})
2. 3. 6. 1. 1 example15

  React的數據攔截原理,原生js實現:通過函數纔可觸發

let obj = {
    x: 1
};
 
function setVal(val) {
    obj.x = val;
    console.log('修改了值');
}
 
setVal(100);

image-20200702175921228

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-1

Branch:branch1

commit description:v0.25-1-example15-1 (展開和收縮面板:React的數據攔截原理)

tag:v0.25-1

  Vue的數據攔截原理,原生js通過defineProperty實現:通過屬性操作即可觸發

let obj = {
    x: 1
};
 
Object.defineProperty(obj, 'x', {
    set() {
        console.log('你修改了值')
    }
});
obj.x = 10;

image-20200702180241437

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-2

Branch:branch1

commit description:v0.25-2-example15-2(展開和收縮面板:Vue的數據攔截原理)

tag:v0.25-2

  React的setState函數

  內部採取(當然還有其他特性,暫時我們先了解這麼多):Object.assign(this.state, {expanded: !this.state.expanded})

  Object.assign()方法用於將所有可枚舉屬性的值從一個或多個源對象複製到目標對象。它將返回目標對象。即對象覆蓋合併。

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展開收縮狀態,默認收縮
        }
        // 一般都在構造函數中綁定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.setState({
             expanded: !this.state.expanded
        });
    }

    render() {
        console.log('render')
        let {title, list} = this.props.data;
        let {expanded} = this.state;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  我們發現setState會自動調用render方法。因此我們只需關注,什麼時候修改state就調用setState方法,如何渲染,就在render方法實現即可。不用關心如何改變state後實時渲染的問題了。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.25-3

Branch:branch1

commit description:v0.25-3-example15-3(展開和收縮面板最終實現)

tag:v0.25-3

2. 3. 6. 1. 2 更新異步

  出於性能考慮,setState 方法的修改並不是立即生效的

// this.state.val = 0
this.setState({
      val: this.state.val + 1
});
// this.state.val 的值並不是 1
console.log(this.state.val);
2. 3. 6. 1. 2. 1 example16
import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.state = {
            expanded: false  // 展開收縮狀態,默認收縮
        }
        // 一般都在構造函數中綁定好this
        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        this.setState({
             expanded: !this.state.expanded
        });
        // 不是立即生效的
        console.log(this.state.expanded);
    }

    render() {
        console.log('render')
        let {title, list} = this.props.data;
        let {expanded} = this.state;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  出於性能考慮,setState 方法的修改並不是立即生效的。和瀏覽器渲染概念類似,並不是一操作完dom,就會生效一樣(刷新頁面),否則性能會非常差。它會蒐集更新,等所有任務完成後,再統一最後處理。因此這個是個坑,你一設置完就去獲取肯定不是我們想要的值。

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.26

Branch:branch1

commit description:v0.26-example16(state更新異步問題)

tag:v0.26

2. 3. 6. 1. 3 更新合併

  React 會把多個 setState 合併成一個調用

// this.state.val = 0
this.setState({
      val: this.state.val + 1
});
this.setState({    // 因爲異步的問題,this.state.val 的值在這裏還是0
      val: this.state.val + 1
});

  上述代碼原理其實就是如下代碼:

Object.assign(
  previousState,
  {val: state.val + 1},
  {val: state.val + 1},
  ...
)

  相當於:

Object.assign(
  this.setState,
  {val: 1},
  {val: 1},
  ...
)
2. 3. 6. 1. 4 回調函數(修改state的另一種形式)

  如果,有多個 setState 的調用,後一次的修改如果需要依賴上一次修改的值,那麼可以使用函數的方式(解決上面的坑),函數不是執行完畢後傳入assign,而是把整個函數傳入,然後在內部執行,最終函數的返回值作爲合併的對象。因爲函數可以依次執行,執行1次改一次,則第一次執行是1,第二次執行的結果是2。

// this.state.val = 0
this.setState((state, props) => {
      return {val: state.val + 1}
});
this.setState((state, props) => {
      return {val: state.val + 1}
});

2. 3. 7 props 與 state 的區別

  state 的主要作用是用於組件保存、控制、修改自己的可變狀態(私有狀態),在組件內部進行初始化,也可以在組件內部進行修改,但是組件外部不能修改組件的 state(與外界無關,但外界可以初始化state

  props 的主要作用是讓使用該組件的父組件可以傳入參數來配置該組件,它是外部傳進來的配置參數,組件內部無法控制也無法修改

  stateprops 都可以決定組件的外觀和顯示狀態。通常,props 做爲不變數據或者初始化數據傳遞給組件,可變狀態使用 state

  能使用 props 就不要使用 state(官方建議),因爲如果一個組件內部私有狀態太多了,它可能就對外部造成很大困擾,而外部使用者並不知道內部發生了什麼,即外部不可控。其實就類似vue,內部語法糖太多了,我們外部僅僅會操作,但不知道內部做了什麼,發生了奇怪的問題,不看源碼,根本解決不了問題。

2. 3. 7. 1 無狀態組件

  沒有 state 的組件,我們稱爲無狀態組件,因爲狀態會帶來複雜性,所以,通常我們推薦使用無狀態組件,也鼓勵編寫無狀態組建。

2. 3. 7. 2 函數式組件

  函數式組件沒有 state (現在的函數式組件有state了,如:react hooks),所以通常我們編寫使用函數式組件來編寫無狀態組件。

2. 3. 8 組件通信與數據流

  在上面的案例中,我們實現了一個多選式的好友列表(每個獨立的面板都有自己獨立的狀態,互不影響)。下面我們再來看另外一種情況:單選式的好友列表(多組列表同時只能有一個爲展開狀態,也就是多個面板之間的狀態是互相影響的)

2. 3. 8. 1 狀態提升(將子組件提升到父組件)

  通過分析可以發現,面板的展開狀態不在是組件內部私有狀態了,多個組件都會受到這個狀態的影響,也就是它們共享了一個狀態。爲了能夠讓多個不同組件共享同一個狀態,這個時候,我們把這個狀態進行提升,交給這些組件最近的公共父組件進行管理

class FriendGroup extends React.Component {
        constructor(props) {
                super(props);
 
          this.changeExpand = this.changeExpand.bind(this);
    }
 
      changeExpand() {
          typeof this.props.onExpand === 'function' &&             this.props.onExpand(this.props.index);
    }
 
      render() {
          let {data, expandedIndex, index} = this.props;
          return (
                <div onClick={this.changeExpand} className={[
                "friend-group",
                expandedIndex === index && "expanded"
            ].join(' ')}>
                <dt>{data.title}</dt>
                {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                })}
            </div>
        );
    }
}
 
class FriendList extends React.Component {
      constructor(props) {
          super(props);
 
          this.state = {
              expandedIndex: 0
        }
 
          this.changeExpandedIndex = this.changeExpandedIndex.bind(this);
    }
 
      changeExpandedIndex(expandedIndex) {
        this.setState({
            expandedIndex
        });
    }
 
      render() {
          let {datas} = this.props;
          return (
              <div onExpand={this.changeExpandedIndex} className="friend-list">
                    {Object.keys(datas).map((key, index) => (
                    <FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />
                ))}
            </div>
        );
    }
}

2. 3. 8. 2 數據流

  在 React.js 中,數據是從上自下流動(傳遞)的,也就是一個父組件可以把它的 state / props 通過 props 傳遞給它的子組件,但是子組件不能修改 props - React.js 是單向數據流,如果子組件需要修改父組件狀態(數據),是通過回調函數方式來完成的。

2. 3. 8. 2. 1 props 函數

  父組件在調用子組件的時候,傳入一個函數類型的 props

<FriendGroup data={datas[key]} index={index} expandedIndex={expandedIndex} key={key} />

  在子組件中根據具體的處理邏輯,調用父組件通過 props 傳入的對應函數,類似 on 事件 的機制,同時子組件可以在調用回調函數的時候,傳入一些數據

2. 3. 8. 2. 2 更新父組件

  在父組件的代碼中,通過 setState 重新渲染父組件,父組件的更新會重新渲染子組件

  注意:以下這種是嵌套的父子關係,我們今天所講的是在其內部屬性裏存着(而不是嵌套父子關係),即普通的父子關係。

<FriendList>
	<group/>
</FriendList>
2. 3. 8. 2. 2. 1 example17

  換需求:點擊選項,同時只能有一個是展開的狀態。

  分析後得出此時的狀態不是私有狀態了(group組件中),因爲別人也需要知道!這個狀態只能交給其父級(group.js上級是index.js)去維護了。

src/App.js

import React from 'react';
import Custom from "./components/custom";
import Custom02 from "./components/custom02";
import FriendList from "./components/friendList2";

let datas = {
    family: {
        title: '家人',
        list: [
            {name: '爸爸'},
            {name: '媽媽'}
        ]
    },
    friend: {
        title: '朋友',
        list: [
            {name: '張三'},
            {name: '李四'},
            {name: '王五'}
        ]
    },
    customer: {
        title: '客戶',
        list: [
            {name: '阿里'},
            {name: '騰訊'},
            {name: '頭條'}
        ]
    }
};

function App() {
  return (
    <div className="App">
        <h1>CSDN</h1>
        <hr />
        {/*{Custom()}*/}
        {/*<Custom datas={datas} v={1} k={'kkk'}/>*/}
        {/*<Custom02 />*/}
        <FriendList datas={datas}/>
    </div>
  );
}

export default App;

app\src\components\friendList2\index.js

import React from 'react';
import './index.css';
import Group from "./group";

export default class FriendList extends React.Component {

    constructor(props) {
        super(props);

        // 狀態
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(false)   // 默認三個導航都是收縮狀態
        };
    }

    render() {
        //解構數據
        let {datas} = this.props;
        let {expanded} = this.state;

        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {Object.keys(datas).map( (key, index) => {
                            let group = datas[key]; // 帶了一個title,和list 的對象
                            return <Group key={key} expanded={expanded[index]} data={group}></Group>
                        }
                    )}
                </div>
            </div>
        )
    }

}

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

image-20200601175918368

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-1

Branch:branch1

commit description:v0.27-1-example17-1 (點擊選項,同時只能有一個是展開的狀態:父級默認三個導航都是收縮狀態)

tag:v0.27-1

  測試一下都設置成true

src/components/friendList2/index.js

constructor(props) {
        super(props);
 
        // 狀態
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(true)
        };
    }

image-20200601180041229

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-2

Branch:branch1

commit description:v0.27-2-example17-2 (點擊選項,同時只能有一個是展開的狀態:測試父級默認三個導航都是張開狀態)

tag:v0.27-2

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        // 子組件內部不允許直接修改父級傳入的props
        this.props.expanded = !this.props.expanded;
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

  這樣子操作會報錯,因爲子組件內部不允許直接修改父級傳入的props。如果可以修改,會導致其他組件用這個數據時出現問題。可在父級中加入事件回調進行修改(就類似士兵執行任務時要實時給領導打報告,不能擅自做決定,這就是規定!這樣就對數據有了安全保障,避免程序出錯。數據由誰維護,修改權就由誰管理。)

image-20200702204802620

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-3

Branch:branch1

commit description:v0.27-3-example17-3 (點擊選項,同時只能有一個是展開的狀態:子級修改父級傳入props報錯)

tag:v0.27-3

app\src\components\friendList2\index.js

import React from 'react';
import './index.css';
import Group from "./group";

export default class FriendList extends React.Component {

    constructor(props) {
        super(props);

        // 狀態
        let len = Object.keys(this.props.datas).length;
        this.state = {
            expanded: new Array(len).fill(false)   // 默認三個導航都是收縮狀態
        };

        this.changeExpanded = this.changeExpanded.bind(this);
    }

    // 傳給子級的事件回調函數:修改子組件的展開狀態
    // index 點擊第幾個導航欄
    changeExpanded(index, val) {
        let expanded = this.state.expanded;
        expanded.fill(false);
        expanded[index] = val;
        this.setState({
            expanded
        });
    }

    render() {
        //解構數據
        let {datas} = this.props;
        let {expanded} = this.state;

        return(
            <div>
                <h2>好友列表</h2>
                <div className="friend-list">
                    {Object.keys(datas).map( (key, index) => {
                            let group = datas[key]; // 帶了一個title,和list 的對象
                            return <Group key={key} index={index} onChange={this.changeExpanded} expanded={expanded[index]} data={group}></Group>
                        }
                    )}
                </div>
            </div>
        )
    }

}

app\src\components\friendList2\group.js

import React from "react";

export default class Group extends React.Component {

    constructor(props) {
        super(props);

        this.toggle = this.toggle.bind(this);
    }

    toggle(e) {
        // 子組件內部不允許直接修改父級傳入的props
        // this.props.expanded = !this.props.expanded;
        if ('function' === typeof this.props.onChange) {
            // 這個函數this指向的是父級的組件this
            this.props.onChange(this.props.index, !this.props.expanded);
        }
    }

    render() {
        let {expanded, data: {title, list}} = this.props;

        return (
            <dl className={['friend-group', expanded ? 'expanded' : ''].join(' ')}>
                <dt onClick={this.toggle}>{title}</dt>
                {
                    list.map( item =>
                        <dd key={item.name}>
                            {item.name}
                        </dd>
                    )
                }
            </dl>
        )
    }

}

參考:https://github.com/6xiaoDi/blog-react-Novice/tree/v0.27-4

Branch:branch1

commit description:v0.27-4-example17-4 (點擊選項,同時只能有一個是展開的狀態:最終實現)

tag:v0.27-4

2. 4 key(上節演示過了!)

class MyComponent extends React.Component {
 
    constructor(props) {
        super(props);
 
        this.state = {
            arr: ['html', 'css', 'javascript']
        }
        this.sort = this.sort.bind(this);
    }
 
    sort() {
        this.setState({
            arr: this.state.arr.sort( _ => Math.random() - .5 )
        });
    }
 
    render() {
        let {arr} = this.state;
        return(
            <div>
                <ul>
                    {arr.map( (v, k) => (
                        <li>
                            <input type="checkbox"/>
                            <span>{v}</span>
                        </li>
                    ) )}
                </ul>
                <button onClick={this.sort}>亂序</button>
            </div>
        );
    }
}
 
ReactDOM.render(
    <MyComponent />,
    document.getElementById('app')
);

通過控制檯查看結構變化

2. 4. 1 渲染優化

  1. 用JS對象模擬DOM樹 : Virtual DOM
  2. 通過 DOM 操作Virtual DOM 生成真實的 DOM
  3. 後期的更新,會比較 新老 Virtual DOM ,通過 diff 算法優化更新操作

2. 4. 1. 1 Virtual DOM

  原生 DOM 對象結構太複雜,同時存在許多情況下用不到的一些特性

  Virtual DOM 本質上與 原生 DOM 沒有區別,都是 JavaScript 對象,只是它的結構更加簡潔

2. 4. 1. 2 Diffing 算法

  實際Diffing 算法只要用到虛擬dom的框架,都有diff算法。

  Web界面由DOM樹來構成,當其中某一部分發生變化時,其實就是對應的某個DOM節點發生了變化。在React中,構建UI界面的思路是由當前狀態決定界面。前後兩個狀態就對應兩套界面,然後由React來比較兩個界面的區別,這就需要對DOM樹進行Diff算法分析。

  傳統算法:找到兩棵樹任意的樹之間最小的修改是一個複雜度爲O(n³)的問題,因爲需要不同的層級。

react用了一種簡單但是強大的技巧,達到了接近O(n)的複雜度。把樹按照層級分解,只比較對應層級的節點,進行對比。

  vue和react的虛擬dom都採用類似的diff算法,核心大概可以歸爲兩點:

  1. 兩個相同的組件產生類似的DOM結構,不同的組件產生不同的DOM結構;

  2. 同一層級的一組節點,他們可以通過唯一的id進行區分。

基於以上兩點,使得虛擬DOMDiff算法的複雜程度從O(n³)降到了O(n)

img

  列表比較,寫一個key屬性幫助React來處理它們之間的對應關係,實際中,在子元素中找到唯一的key通常很容易。如希望插入一個節點,傳統方法則是類似數組插入一樣,在中間插入一個值,後面的值逐個往後便宜,這樣效率低下。所以我們需要使用key來給每個節點做一個唯一的標識,Diff算法就可以正確的識別此節點,找到正確的位置區插入新的節點,這樣就避免了沒有意義的節點刷新了。

image-20200702195257811

  Components比較,React app通常由用戶定義的component組合而成。通常結果是一個主要是很多div組成的樹。

  這個信息也被React的diff算法考慮進去了,React只會匹配相同類型(ES6 class)的component

image-20200702195931439

  合併操作,當調用componentsetstate方法的時候,React將其標記爲dirty。 到每一個事件循環結束,React檢查所有標記 dirtycomponent重新繪製。這就不會每次執行setstate方法的時候都發生渲染了,這樣效率會極其低下了。

image-20200702200744163

  正常情形下,假如紅色節點發生改變,其下面的所有子節點都得重新刷新,但假如子節點沒有變化就沒必要刷新,因此這樣效率極其低下。假設只判斷出藍色部分需要刷新,則對應去重新渲染。

  選擇性子樹渲染,在組件上實現下面的生命週期方法

boolean shouldComponentUpdate(object nextProps, object nextState)

  根據 component的上面這個生命週期(後面再說生命週期),將前一個和下一個props/state,進行比較,來判斷子節點是否變化。這就告訴 React這個 component有沒有更新,需不需要重新繪製。實現得好的話,可以帶來巨大的性能提升。

image-20200702200802025



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