1. 程式人生 > >你要的 React 面試知識點,都在這了

你要的 React 面試知識點,都在這了

摘要: 問題很詳細,插圖很好看。

Fundebug經授權轉載,版權歸原作者所有。

React是流行的javascript框架之一,在2019年及以後將會更加流行。React於2013年首次釋出,多年來廣受歡迎。它是一個宣告性的、基於元件的、用於構建使用者介面的高效javascript庫。

以下是面試前必須瞭解的話題。

  • 什麼是宣告式程式設計
  • 宣告式程式設計 vs 指令式程式設計
  • 什麼是函數語言程式設計
  • 什麼是元件設計模式
  • React 是什麼
  • React 和 Angular 有什麼不同
  • 什麼是虛擬DOM及其工作原理
  • 什麼是JSX
  • 元件和不同型別
  • Props 和 State
  • 什麼是 PropTypes
  • 如何更新狀態和不更新狀態
  • 元件生命週期方法
  • 超越繼承的組合
  • 如何在React中應用樣式
  • 什麼是Redux及其工作原理
  • 什麼是React路由器及其工作原理
  • 什麼是錯誤邊界
  • 什麼是 Fragments
  • 什麼是傳送門(Portals)
  • 什麼是 Context
  • 什麼是 Hooks
  • 如何提高效能
  • 如何在重新載入頁面時保留資料
  • 如何從React中呼叫API
  • 總結

什麼是宣告式程式設計

宣告式程式設計是一種程式設計正規化,它關注的是你要做什麼,而不是如何做。它表達邏輯而不顯式地定義步驟。這意味著我們需要根據邏輯的計算來宣告要顯示的元件。它沒有描述控制流步驟。宣告式程式設計的例子有HTML、SQL等

HTML file

// HTML
<div>
  <p>Declarative Programming</p>
</div>

SQL file

select * from studens where firstName = 'declarative';

宣告式程式設計 vs 指令式程式設計

宣告式程式設計的編寫方式描述了應該做什麼,而指令式程式設計描述瞭如何做。在宣告式程式設計中,讓編譯器決定如何做事情。宣告性程式很容易推理,因為程式碼本身描述了它在做什麼。

下面是一個例子,陣列中的每個元素都乘以 2,我們使用宣告式map函式,讓編譯器來完成其餘的工作,而使用命令式,需要編寫所有的流程步驟。

const numbers = [1,2,3,4,5];

// 宣告式
const doubleWithDec = numbers.map(number => number * 2);

console.log(doubleWithDec)

// 命令式
const doubleWithImp = [];
for(let i=0; i<numbers.length; i++) {
    const numberdouble = numbers[i] * 2;
    doubleWithImp.push(numberdouble)
}

console.log(doubleWithImp)

什麼是函數語言程式設計

函數語言程式設計是宣告式程式設計的一部分。javascript中的函式是第一類公民,這意味著函式是資料,你可以像儲存變數一樣在應用程式中儲存、檢索和傳遞這些函式。

函數語言程式設計有些核心的概念,如下:

  • 不可變性(Immutability)
  • 純函式(Pure Functions)
  • 資料轉換(Data Transformations)
  • 高階函式 (Higher-Order Functions)
  • 遞迴
  • 組合

不可變性(Immutability)

不可變性意味著不可改變。 在函數語言程式設計中,你無法更改資料,也不能更改。 如果要改變或更改資料,則必須複製資料副本來更改。

例如,這是一個student物件和changeName函式,如果要更改學生的名稱,則需要先複製 student 物件,然後返回新物件。

在javascript中,函式引數是對實際資料的引用,你不應該使用 student.firstName =“testing11”,這會改變實際的student 物件,應該使用Object.assign複製物件並返回新物件。

let student = {
    firstName: "testing",
    lastName: "testing",
    marks: 500
}

function changeName(student) {
    // student.firstName = "testing11" //should not do it
    let copiedStudent = Object.assign({}, student);
    copiedStudent.firstName = "testing11";
    return copiedStudent;
}

console.log(changeName(student));

console.log(student);

純函式

純函式是始終接受一個或多個引數並計算引數並返回資料或函式的函式。 它沒有副作用,例如設定全域性狀態,更改應用程式狀態,它總是將引數視為不可變資料。

我想使用 appendAddress 的函式向student物件新增一個地址。 如果使用非純函式,它沒有引數,直接更改 student 物件來更改全域性狀態。

使用純函式,它接受引數,基於引數計算,返回一個新物件而不修改引數。

let student = {
    firstName: "testing",
    lastName: "testing",
    marks: 500
}

// 非純函式
function appendAddress() {
    student.address = {streetNumber:"0000", streetName: "first", city:"somecity"};
}

console.log(appendAddress());

// 純函式
function appendAddress(student) {
    let copystudent = Object.assign({}, student);
    copystudent.address = {streetNumber:"0000", streetName: "first", city:"somecity"};
    return copystudent;
}

console.log(appendAddress(student));

console.log(student);

資料轉換

我們講了很多關於不可變性的內容,如果資料是不可變的,我們如何改變資料。如上所述,我們總是生成原始資料的轉換副本,而不是直接更改原始資料。

再介紹一些 javascript內建函式,當然還有很多其他的函式,這裡有一些例子。所有這些函式都不改變現有的資料,而是返回新的陣列或物件。

let cities = ["irving", "lowell", "houston"];

// we can get the comma separated list
console.log(cities.join(','))
// irving,lowell,houston

// if we want to get cities start with i
const citiesI = cities.filter(city => city[0] === "i");
console.log(citiesI)
// [ 'irving' ]

// if we want to capitalize all the cities
const citiesC = cities.map(city => city.toUpperCase());
console.log(citiesC)
// [ 'IRVING', 'LOWELL', 'HOUSTON' ]

高階函式

高階函式是將函式作為引數或返回函式的函式,或者有時它們都有。 這些高階函式可以操縱其他函式。

Array.map,Array.filter和Array.reduce是高階函式,因為它們將函式作為引數。

const numbers = [10,20,40,50,60,70,80]

const out1 = numbers.map(num => num * 100);
console.log(out1);
// [ 1000, 2000, 4000, 5000, 6000, 7000, 8000 ]

const out2 = numbers.filter(num => num > 50);
console.log(out2);
// [ 60, 70, 80 ]

const out3 = numbers.reduce((out,num) => out + num);
console.log(out3);
// 330

下面是另一個名為isPersonOld的高階函式示例,該函式接受另外兩個函式,分別是 messageisYoung

const isYoung = age => age < 25;

const message = msg => "He is "+ msg;

function isPersonOld(age, isYoung, message) {
    const returnMessage = isYoung(age)?message("young"):message("old");
    return returnMessage;
}

// passing functions as an arguments
console.log(isPersonOld(13,isYoung,message))
// He is young

遞迴

遞迴是一種函式在滿足一定條件之前呼叫自身的技術。只要可能,最好使用遞迴而不是迴圈。你必須注意這一點,瀏覽器不能處理太多遞迴和丟擲錯誤。

下面是一個演示遞迴的例子,在這個遞迴中,列印一個類似於樓梯的名稱。我們也可以使用for迴圈,但只要可能,我們更喜歡遞迴。

function printMyName(name, count) {
    if(count <= name.length) {
        console.log(name.substring(0,count));
        printMyName(name, ++count);
    }
}

console.log(printMyName("Bhargav", 1));

/*
B
Bh
Bha
Bhar
Bharg
Bharga
Bhargav
*/

// withotu recursion
var name = "Bhargav"
var output = "";
for(let i=0; i<name.length; i++) {
    output = output + name[i];
    console.log(output);
}


組合

在React中,我們將功能劃分為小型可重用的純函式,我們必須將所有這些可重用的函式放在一起,最終使其成為產品。 將所有較小的函式組合成更大的函式,最終,得到一個應用程式,這稱為組合

實現組合有許多不同方法。 我們從Javascript中瞭解到的一種常見方法是連結。 連結是一種使用表示法呼叫前一個函式的返回值的函式的方法。

這是一個例子。 我們有一個name,如果firstNamelastName大於5個單詞的大寫字母,剛返回,並且列印名稱的名稱和長度。

const name = "Bhargav Bachina";

const output = name.split(" ")
    .filter(name => name.length > 5)
    .map(val => {
    val = val.toUpperCase();
    console.log("Name:::::"+val);
    console.log("Count::::"+val.length);
    return val;
});

console.log(output)
/*
Name:::::BHARGAV
Count::::7
Name:::::BACHINA
Count::::7
[ 'BHARGAV', 'BACHINA' ]
*/

在React中,我們使用了不同於連結的方法,因為如果有30個這樣的函式,就很難進行連結。這裡的目的是將所有更簡單的函式組合起來生成一個更高階的函式。

const name = compose(
    splitmyName,
    countEachName,
    comvertUpperCase,
    returnName
)

console.log(name);

什麼是 React

React是一個簡單的javascript UI庫,用於構建高效、快速的使用者介面。它是一個輕量級庫,因此很受歡迎。它遵循元件設計模式、宣告式程式設計正規化和函數語言程式設計概念,以使前端應用程式更高效。它使用虛擬DOM來有效地操作DOM。它遵循從高階元件到低階元件的單向資料流。

React 與 Angular 有何不同?

Angular是一個成熟的MVC框架,帶有很多特定的特性,比如服務、指令、模板、模組、解析器等等。React是一個非常輕量級的庫,它只關注MVC的檢視部分。

Angular遵循兩個方向的資料流,而React遵循從上到下的單向資料流。React在開發特性時給了開發人員很大的自由,例如,呼叫API的方式、路由等等。我們不需要包括路由器庫,除非我們需要它在我們的專案。

什麼是Virtual DOM及其工作原理

React 使用 Virtual DOM 來更新真正的 DOM,從而提高效率和速度。 我們來詳細討論這些。

什麼是Virtual DOM

瀏覽器遵循HTML指令來構造文件物件模型(DOM)。當瀏覽器載入HTML並呈現使用者介面時,HTML文件中的所有元素都變成DOM元素。

DOM是從根元素開始的元素層次結構。例如,看看下面的HTML。

<div>
    <div>
        <h1>This is heading</h1>
        <p>this is paragraph</p>
        <div>
            <p>This is just a paragraon</p>
        </div>
    </div>
    <div>
        <h1>This is heading</h1>
        <p>this is paragraph</p>
        <div>
            <p>This is just a paragraon</p>
        </div>
    </div>
    <div>
        <h1>This is heading</h1>
        <p>this is paragraph</p>
        <div>
            <p>This is just a paragraon</p>
        </div>
    </div>
</div>

當在瀏覽器中載入這個HTML時,所有這些HTML元素都被轉換成DOM元素,如下所示

![

當涉及到SPA應用程式時,首次載入index.html,並在index.html本身中載入更新後的資料或另一個html。當用戶瀏覽站點時,我們使用新內容更新相同的index.html。每當DOM發生更改時,瀏覽器都需要重新計算CSS、進行佈局並重新繪製web頁面。

React 使用 Virtual DOM 有效地重建 DOM。 對於我們來說,這使得DOM操作的一項非常複雜和耗時的任務變得更加容易。 React從開發人員那裡抽象出所有這些,以便在Virtual DOM的幫助下構建高效的UI。

虛擬DOM是如何工作的

虛擬DOM只不過是真實 DOM 的 javascript物件表示。 與更新真實 DOM 相比,更新 javascript 物件更容易,更快捷。 考慮到這一點,讓我們看看它是如何工作的。

React將整個DOM副本儲存為虛擬DOM

![

每當有更新時,它都會維護兩個虛擬DOM,以比較之前的狀態和當前狀態,並確定哪些物件已被更改。 例如,段落文字更改為更改。

現在,它通過比較兩個虛擬DOM 差異,並將這些變化更新到實際DOM

![

一旦真正的DOM更新,它也會更新UI

![

什麼是 JSX

JSX是javascript的語法擴充套件。它就像一個擁有javascript全部功能的模板語言。它生成React元素,這些元素將在DOM中呈現。React建議在元件使用JSX。在JSX中,我們結合了javascript和HTML,並生成了可以在DOM中呈現的react元素。

下面是JSX的一個例子。我們可以看到如何將javascript和HTML結合起來。如果HTML中包含任何動態變數,我們應該使用表示式{}

import React from 'react';

export const Header = () => {

    const heading = 'TODO App'

    return(
        <div style={{backgroundColor:'orange'}}>
            <h1>{heading}</h1>
        </div>
    )
}

程式碼部署後可能存在的BUG沒法實時知道,事後為了解決這些BUG,花了大量的時間進行log 除錯,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug

元件和不同型別

React 中一切都是元件。 我們通常將應用程式的整個邏輯分解為小的單個部分。 我們將每個單獨的部分稱為元件。 通常,元件是一個javascript函式,它接受輸入,處理它並返回在UI中呈現的React元素。

在React中有不同型別的元件。讓我們詳細看看。

函式/無狀態/展示元件

函式或無狀態元件是一個純函式,它可接受接受引數,並返回react元素。這些都是沒有任何副作用的純函式。這些元件沒有狀態或生命週期方法,這裡有一個例子。

import React from 'react';
import Jumbotron from 'react-bootstrap/Jumbotron';

export const Header = () => {
    return(
        <Jumbotron style={{backgroundColor:'orange'}}>
            <h1>TODO App</h1>
        </Jumbotron>
    )
}


類/有狀態元件

類或有狀態元件具有狀態和生命週期方可能通過setState()方法更改元件的狀態。類元件是通過擴充套件React建立的。它在建構函式中初始化,也可能有子元件,這裡有一個例子。

import React from 'react';
import '../App.css';
import { ToDoForm } from './todoform';
import { ToDolist } from './todolist';

export class Dashboard extends React.Component {

  constructor(props){
    super(props);

    this.state = {

    }
  }
  
  render() {
    return (
      <div className="dashboard"> 
          <ToDoForm />
          <ToDolist />
      </div>
    );
  }
}

受控元件

受控元件是在 React 中處理輸入表單的一種技術。表單元素通常維護它們自己的狀態,而react則在元件的狀態屬性中維護狀態。我們可以將兩者結合起來控制輸入表單。這稱為受控元件。因此,在受控元件表單中,資料由React元件處理。

這裡有一個例子。當用戶在 todo 項中輸入名稱時,呼叫一個javascript函式handleChange捕捉每個輸入的資料並將其放入狀態,這樣就在 handleSubmit中的使用資料。

import React from 'react';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

export class ToDoForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {value: ''};
  
      this.handleChange = this.handleChange.bind(this);
      this.handleSubmit = this.handleSubmit.bind(this);
    }
  
    handleChange(event) {
      this.setState({value: event.target.value});
    }
  
    handleSubmit(event) {
      alert('A name was submitted: ' + this.state.value);
      event.preventDefault();
    }
  
    render() {
      return (
          <div className="todoform">
            <Form>
                <Form.Group as={Row} controlId="formHorizontalEmail">
                    <Form.Label column sm={2}>
                    <span className="item">Item</span>
                    </Form.Label>
                    <Col sm={5}>
                        <Form.Control type="text" placeholder="Todo Item" />
                    </Col>
                    <Col sm={5}>
                        <Button variant="primary" type="submit">Add</Button>
                    </Col>
                </Form.Group>
            </Form>
         </div>
      );
    }
  }

非受控元件

大多數情況下,建議使用受控元件。有一種稱為非受控元件的方法可以通過使用Ref來處理表單資料。在非受控元件中,Ref用於直接從DOM訪問表單值,而不是事件處理程式。

我們使用Ref構建了相同的表單,而不是使用React狀態。 我們使用React.createRef() 定義Ref並傳遞該輸入表單並直接從handleSubmit方法中的DOM訪問表單值。

import React from 'react';
import Form from 'react-bootstrap/Form';
import Button from 'react-bootstrap/Button';
import Row from 'react-bootstrap/Row';
import Col from 'react-bootstrap/Col';

export class ToDoForm extends React.Component {
    constructor(props) {
      super(props);
      this.state = {value: ''};
      this.input = React.createRef();
  
      this.handleSubmit = this.handleSubmit.bind(this);
    }
  
    handleSubmit(event) {
      alert('A name was submitted: ' + this.input.current.value);
      event.preventDefault();
    }
  
    render() {
      return (
          <div className="todoform">
            <Form>
                <Form.Group as={Row} controlId="formHorizontalEmail">
                    <Form.Label column sm={2}>
                    <span className="item">Item</span>
                    </Form.Label>
                    <Col sm={5}>
                        <Form.Control type="text" placeholder="Todo Item" ref={this.input}/>
                    </Col>
                    <Col sm={5}>
                        <Button variant="primary" onClick={this.handleSubmit} type="submit">Add</Button>
                    </Col>
                </Form.Group>
            </Form>
         </div>
      );
    }
  }

容器元件

容器元件是處理獲取資料、訂閱 redux 儲存等的元件。它們包含展示元件和其他容器元件,但是裡面從來沒有html。

高階元件

高階元件是將元件作為引數並生成另一個元件的元件。 Redux connect是高階元件的示例。 這是一種用於生成可重用元件的強大技術。

Props 和 State

Props 是隻讀屬性,傳遞給元件以呈現UI和狀態,我們可以隨時間更改元件的輸出。

下面是一個類元件的示例,它在建構函式中定義了propsstate,每當使用this.setState() 修改狀態時,將再次呼叫 render( ) 函式來更改UI中元件的輸出。

import React from 'react';
import '../App.css';

export class Dashboard extends React.Component {

  constructor(props){
    super(props);

    this.state = {
        name: "some name"
    }
  }

  render() {

    // reading state
    const name = this.state.name;

    //reading props
    const address = this.props.address;

    return (
      <div className="dashboard"> 
          {name}
          {address}
      </div>
    );
  }
}

什麼是PropTypes

隨著時間的推移,應用程式會變得越來越大,因此型別檢查非常重要。PropTypes為元件提供型別檢查,併為其他開發人員提供很好的文件。如果react專案不使用 Typescript,建議為元件新增 PropTypes

如果元件沒有收到任何 props,我們還可以為每個元件定義要顯示的預設 props。這裡有一個例子。UserDisplay有三個 prop:nameaddressage,我們正在為它們定義預設的props 和 prop型別。

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

export const UserDisplay = ({name, address, age}) => {

    UserDisplay.defaultProps = {
        name: 'myname',
        age: 100,
        address: "0000 onestreet"
    };

    return (
        <>
            <div>
                <div class="label">Name:</div>
                <div>{name}</div>
            </div>
            <div>
                <div class="label">Address:</div>
                <div>{address}</div>
            </div>
            <div>
                <div class="label">Age:</div>
                <div>{age}</div>
            </div>
        </>
    )
}

UserDisplay.propTypes = {
    name: PropTypes.string.isRequired,
    address: PropTypes.objectOf(PropTypes.string),
    age: PropTypes.number.isRequired
}

如何更新狀態以及如何不更新

你不應該直接修改狀態。可以在建構函式中定義狀態值。直接使用狀態不會觸發重新渲染。React 使用this.setState()時合併狀態。

//  錯誤方式
this.state.name = "some name"
//  正確方式
this.setState({name:"some name"})

使用this.setState()的第二種形式總是更安全的,因為更新的props和狀態是非同步的。這裡,我們根據這些 props 更新狀態。

// 錯誤方式
this.setState({
    timesVisited: this.state.timesVisited + this.props.count
})
// 正確方式
this.setState((state, props) => {
    timesVisited: state.timesVisited + props.count
});

元件生命週期方法

元件在進入和離開DOM時要經歷一系列生命週期方法,下面是這些生命週期方法。

componentWillMount()

在渲染前呼叫,在客戶端也在服務端,它只發生一次。

componentDidMount()

在第一次渲染後呼叫,只在客戶端。之後元件已經生成了對應的DOM結構,可以通過this.getDOMNode()來進行訪問。 如果你想和其他JavaScript框架一起使用,可以在這個方法中呼叫setTimeout, setInterval或者傳送AJAX請求等操作(防止異部操作阻塞UI)。

componentWillReceiveProps()

在元件接收到一個新的 prop (更新後)時被呼叫。這個方法在初始化render時不會被呼叫。

shouldComponentUpdate()

返回一個布林值。在元件接收到新的props或者state時被呼叫。在初始化時或者使用forceUpdate時不被呼叫。 可以在你確認不需要更新元件時使用。

componentWillUpdate()

在元件接收到新的props或者state但還沒有render時被呼叫。在初始化時不會被呼叫。

componentDidUpdate()

在元件完成更新後立即呼叫。在初始化時不會被呼叫。

componentWillUnMount()

件從 DOM 中移除的時候立刻被呼叫。

getDerivedStateFromError()

這個生命週期方法在ErrorBoundary類中使用。實際上,如果使用這個生命週期方法,任何類都會變成ErrorBoundary。這用於在元件樹中出現錯誤時呈現回退UI,而不是在螢幕上顯示一些奇怪的錯誤。

componentDidCatch()

這個生命週期方法在ErrorBoundary類中使用。實際上,如果使用這個生命週期方法,任何類都會變成ErrorBoundary。這用於在元件樹中出現錯誤時記錄錯誤。

超越繼承的組合

在React中,我們總是使用組合而不是繼承。我們已經在函數語言程式設計部分討論了什麼是組合。這是一種結合簡單的可重用函式來生成高階元件的技術。下面是一個組合的例子,我們在 dashboard 元件中使用兩個小元件todoFormtodoList

import React from 'react';
import '../App.css';
import { ToDoForm } from './todoform';
import { ToDolist } from './todolist';

export class Dashboard extends React.Component {

  render() {
    return (
      <div className="dashboard"> 
          <ToDoForm />
          <ToDolist />
      </div>
    );
  }
}


如何在React中應用樣式

將樣式應用於React元件有三種方法。

外部樣式表

在此方法中,你可以將外部樣式表匯入到元件使用類中。 但是你應該使用className而不是class來為React元素應用樣式, 這裡有一個例子。

import React from 'react';
import './App.css';
import { Header } from './header/header';
import { Footer } from './footer/footer';
import { Dashboard } from './dashboard/dashboard';
import { UserDisplay } from './userdisplay';

function App() {
  return (
    <div className="App">
      <Header />
      <Dashboard />
      <UserDisplay />
      <Footer />
    </div>
  );
}

export default App;

內聯樣式

在這個方法中,我們可以直接將 props 傳遞給HTML元素,屬性為style。這裡有一個例子。這裡需要注意的重要一點是,我們將javascript物件傳遞給style,這就是為什麼我們使用 backgroundColor 而不是CSS方法backbackground -color

import React from 'react';

export const Header = () => {

    const heading = 'TODO App'

    return(
        <div style={{backgroundColor:'orange'}}>
            <h1>{heading}</h1>
        </div>
    )
}

定義樣式物件並使用它

因為我們將javascript物件傳遞給style屬性,所以我們可以在元件中定義一個style物件並使用它。下面是一個示例,你也可以將此物件作為 props 傳遞到元件樹中。

import React from 'react';

const footerStyle = {
    width: '100%',
    backgroundColor: 'green',
    padding: '50px',
    font: '30px',
    color: 'white',
    fontWeight: 'bold'
}

export const Footer = () => {
    return(
        <div style={footerStyle}>
            All Rights Reserved 2019
        </div>
    )
}

什麼是Redux及其工作原理

Redux 是 React的一個狀態管理庫,它基於flux。 Redux簡化了React中的單向資料流。 Redux將狀態管理完全從React中抽象出來。

它是如何工作的

在React中,元件連線到 redux ,如果要訪問 redux,需要派出一個包含 id和負載(payload) 的 action。action 中的 payload 是可選的,action 將其轉發給 Reducer。

reducer收到action時,通過 swithc...case 語法比較 actiontype。 匹配時,更新對應的內容返回新的 state

Redux狀態更改時,連線到Redux的元件將接收新的狀態作為props。當元件接收到這些props時,它將進入更新階段並重新渲染 UI。

![

Redux 迴圈細節

讓我們詳細看看整個redux 迴圈細節。

![

Action: Action 只是一個簡單的json物件,type 和有payload作為鍵。type 是必須要有的,payload是可選的。下面是一個 action 的例子。

// action

{ 
  type:"SEND_EMAIL", 
  payload: data
};

Action Creators:這些是建立Actions的函式,因此我們在派發action時不必在元件中手動編寫每個 action。 以下是 action creator 的示例。

// action creator

export function sendEamil(data) {
    return { type:"SEND_EMAIL", payload: data};
}

Reducers:Reducers 是純函式,它將 action和當前 state 作為引數,計算必要的邏輯並返回一個新r的state。 這些 Reducers 沒有任何副作用。 它不會改變 state 而是總是返回 state

export default function emailReducer(state = [], action){
 
  switch(action.type) {
      case "SEND_EMAIL":  return Object.assign({}, state, {
       email: action.payload
      });
      default: return state;
  }
}

元件如何與 redux 進行連線

mapStateToProps:此函式將state對映到 props 上,因此只要state發生變化,新 state 會重新對映到 props。 這是訂閱store的方式。

mapDispatchToProps:此函式用於將 action creators 繫結到你的props 。以便我們可以在第12行中使用This . props.actions.sendemail()來派發一個動作。

connectbindActionCreators來自 redux。 前者用於連線 store ,如第22行,後者用於將 action creators 繫結到你的 props ,如第20行。

// import connect
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'

// import action creators
import * as userActions from '../../../actions/userActions';

export class User extends React.Component {
  
    handleSubmit() {
        // dispatch an action
        this.props.actions.sendEmail(this.state.email);
    }
  
}

// you are mapping you state props
const mapStateToProps = (state, ownProps) => ({user: state.user})
// you are binding your action creators to your props
const mapDispatchToProps = (dispatch) => ({actions: bindActionCreators(userActions, dispatch)})

export default connect(mapStateToProps, mapDispatchToProps)(User);

什麼是 React Router Dom 及其工作原理

react-router-dom是應用程式中路由的庫。 React庫中沒有路由功能,需要單獨安裝react-router-dom

react-router-dom 提供兩個路由器BrowserRouterHashRoauter。前者基於rul的pathname段,後者基於hash段。

 前者:http://127.0.0.1:3000/article/num1

 後者:http://127.0.0.1:3000/#/article/num1(不一定是這樣,但#是少不了的)

react-router-dom 元件

  • BrowserRouterHashRouter 是路由器。
  • Route 用於路由匹配。
  • Link 元件用於在應用程式中建立連結。 它將在HTML中渲染為錨標記。
  • NavLink是突出顯示當前活動連結的特殊連結。
  • Switch 不是必需的,但在組合路由時很有用。
  • Redirect 用於強制路由重定向

下面是元件中的LinkNavLinkRedirect 的例子

// normal link
<Link to="/gotoA">Home</Link>

// link which highlights currentlu active route with the given class name
<NavLink to="/gotoB" activeClassName="active">
  React
</NavLink>

// you can redirect to this url
<Redirect to="/gotoC" />

以下是 react router 元件的示例。 如果你檢視下面的示例,我們將匹配路徑並使用SwitchRoute呈現相應的元件。

import React from 'react'
// import react router DOM elements
import { Switch, Route, Redirect } from 'react-router-dom'
import ComponentA from '../common/compa'
import ComponentB from '../common/compb'
import ComponentC from '../common/compc'
import ComponentD from '../common/compd'
import ComponentE from '../common/compe'


const Layout = ({ match }) => {
    return(
        <div className="">
            <Switch>
                <Route exact path={`${match.path}/gotoA`} component={ComponentA} />
                <Route path={`${match.path}/gotoB`} component={ComponentB} />
                <Route path={`${match.path}/gotoC`} component={ComponentC} />
                <Route path={`${match.path}/gotoD`} component={ComponentD} />
                <Route path={`${match.path}/gotoE`} component={ComponentE} />
            </Switch>
        </div>
    )}

export default Layout

什麼是錯誤邊界

在 React 中,我們通常有一個元件樹。如果任何一個元件發生錯誤,它將破壞整個元件樹。沒有辦法捕捉這些錯誤,我們可以用錯誤邊界優雅地處理這些錯誤。

錯誤邊界有兩個作用

  • 如果發生錯誤,顯示回退UI
  • 記錄錯誤

下面是ErrorBoundary類的一個例子。如果類實現了 getDerivedStateFromErrorcomponentDidCatch 這兩個生命週期方法的任何一下,,那麼這個類就會成為ErrorBoundary。前者返回{hasError: true}來呈現回退UI,後者用於記錄錯誤。

import React from 'react'

export class ErrorBoundary extends React.Component {
    constructor(props) {
      super(props);
      this.state = { hasError: false };
    }
  
    static getDerivedStateFromError(error) {
      // Update state so the next render will show the fallback UI.
      return { hasError: true };
    }
  
    componentDidCatch(error, info) {
      // You can also log the error to an error reporting service
      console.log('Error::::', error);
    }
  
    render() {
      if (this.state.hasError) {
        // You can render any custom fallback UI
        return <h1>OOPS!. WE ARE LOOKING INTO IT.</h1>;
      }
  
      return this.props.children; 
    }
  }

以下是我們如何在其中一個元件中使用ErrorBoundary。使用ErrorBoundary類包裹 ToDoFormToDoList。 如果這些元件中發生任何錯誤,我們會記錄錯誤並顯示回退UI。

import React from 'react';
import '../App.css';
import { ToDoForm } from './todoform';
import { ToDolist } from './todolist';
import { ErrorBoundary } from '../errorboundary';

export class Dashboard extends React.Component {

  render() {
    return (
      <div className="dashboard"> 
        <ErrorBoundary>
          <ToDoForm />
          <ToDolist />
        </ErrorBoundary>
      </div>
    );
  }
}

什麼是 Fragments

在React中,我們需要有一個父元素,同時從元件返回React元素。有時在DOM中新增額外的節點會很煩人。使用 Fragments,我們不需要在DOM中新增額外的節點。我們只需要用 React.Fragment 或才簡寫 <> 來包裹內容就行了。如下 所示:

 // Without Fragments   
return (
    <div>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </div>
)

// With Fragments   
  return (
    <React.Fragment>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </React.Fragment>
  )

  // shorthand notation Fragments   
  return (
    <>
       <CompoentA />
       <CompoentB />
       <CompoentC />
    </>
  )

什麼是傳送門(Portals)

預設情況下,所有子元件都在UI上呈現,具體取決於元件層次結構。Portal 提供了一種將子節點渲染到存在於父元件以外的 DOM 節點的優秀的方案。

這裡有一個例子。預設情況下,父元件在DOM層次結構中有子元件。

![

我們可以將 children 元件移出parent 元件並將其附加 idsomeid 的 Dom 節點下。

首先,獲取 id 為 someid,我們在constrcutorand中建立一個元素div,將child附加到componentDidMount中的someRoot。 最後,我們在ReactDOM.createPortal(this.props.childen),domnode的幫助下將子節點傳遞給該特定DOM節點。

首先,先獲取 id 為someid DOM元素,接著在建構函式中建立一個元素div,在 componentDidMount方法中將 someRoot 放到 div 中 。 最後,通過 ReactDOM.createPortal(this.props.childen), domnode)children 傳遞到對應的節點下。

const someRoot = document.getElementById('someid');

class Modal extends React.Component {
  constructor(props) {
    super(props);
    this.el = document.createElement('div');
  }

  componentDidMount() {
    someRoot.appendChild(this.el);
  }

  componentWillUnmount() {
    someRoot.removeChild(this.el);
  }

  render() {
    return ReactDOM.createPortal(
      this.props.children,
      this.el,
    );
  }
}

![

什麼是上下文

有時我們必須將props 傳遞給元件樹,即使所有中間元件都不需要這些props 。上下文是一種傳遞props 的方法,而不用在每一層傳遞元件樹。

什麼是 Hooks

Hooks 是React版本16.8中的新功能。 請記住,我們不能在函式元件中使用state ,因為它們不是類元件。Hooks 讓我們在函式元件中可以使用state 和其他功能。

目前沒有重大變化,我們不必放棄類元件。

Hook 不會影響你對 React 概念的理解。 恰恰相反,Hook 為已知的 React 概念提供了更直接的 API:props, state,context,refs 以及生命週期。稍後我們將看到,Hook 還提供了一種更強大的方式來組合他們。

我們可以使用一些鉤子,例如useState,useEffect,useContext,useReducer等。

下面是 Hooks 的基本規則

  • Hooks 應該在外層使用,不應該在迴圈,條件或巢狀函式中使用
  • Hooks 應該只在函式元件中使用。

讓我們看一個例子來理解 hooks。 這是一個函式元件,它採用props並在UI上顯示這些props。 在useState鉤子的幫助下,我們將這個函式元件轉換為有狀態元件。 首先,我們在第5行定義狀態,這相當於

constructor(props) {
 super(props);
 this.state = {
     name:'myname', age:10, address:'0000 one street'
 }
}

useState返回兩個項,一個是user,另一個是setUser函式。 user 是一個可以在沒有 this關鍵字的情況下直接使用的物件,setUser是一個可以用來設定使用者點選第21行按鈕的狀態的函式,該函式等效於以下內容。

this.setState({name:'name changed'})

import React, { useState } from "react";

export const UserDisplay = ({ name, address, age }) => {
    const [user, setUser] = useState({
        name: "myname",
        age: 10,
        address: "0000 onestreet"
    });

    return (
        <>
            <div>
                <div class="label">Name:</div>
                <div>{user.name}</div>
            </div>
            <div>
                <div class="label">Address:</div>
                <div>{user.address}</div>
            </div>
            <div>
                <div class="label">Age:</div>
                <div>{user.age}</div>
            </div>
            <button onClick={() => setUser({ name: "name changed" })}>
                Click me
            </button>
        </>
    );
};

如何提高效能

我們可以通過多種方式提高應用效能,以下這些比較重要:

  • 適當地使用shouldComponentUpdate生命週期方法。 它避免了子元件的不必要的渲染。 如果樹中有100個元件,則不重新渲染整個元件樹來提高應用程式效能。
  • 使用create-react-app來構建專案,這會建立整個專案結構,並進行大量優化。
  • 不可變性是提高效能的關鍵。不要對資料進行修改,而是始終在現有集合的基礎上建立新的集合,以保持儘可能少的複製,從而提高效能。
  • 在顯示列表或表格時始終使用 Keys,這會讓 React 的更新速度更快
  • 程式碼分離是將程式碼插入到單獨的檔案中,只加載模組或部分所需的檔案的技術。

如何在重新載入頁面時保留資料

單頁應用程式首先在DOM中載入index.html,然後在使用者瀏覽頁面時載入內容,或者從同一index.html中的後端API獲取任何資料。

如果通過點選瀏覽器中的重新載入按鈕重新載入頁面index.html,整個React應用程式將重新載入,我們將丟失應用程式的狀態。 如何保留應用狀態?

每當重新載入應用程式時,我們使用瀏覽器localstorage來儲存應用程式的狀態。我們將整個儲存資料儲存在localstorage中,每當有頁面重新整理或重新載入時,我們從localstorage載入狀態。

![

如何在React進行API呼叫

我們使用redux-thunk在React中呼叫API。因為reduce是純函式,所以沒有副作用,比如呼叫API。

因此,我們必須使用redux-thunk從 action creators 那裡進行 API 呼叫。Action creator 派發一個action,將來自API的資料放入action 的 payload 中。Reducers 接收我們在上面的redux迴圈中討論的資料,其餘的過程也是相同的。

![

redux-thunk是一箇中間件。一旦它被引入到專案中,每次派發一個action時,都會通過thunk傳遞。如果它是一個函式,它只是等待函式處理並返回響應。如果它不是一個函式,它只是正常處理。

這裡有一個例子。sendEmailAPI是從元件中呼叫的函式,它接受一個數據並返回一個函式,其中dispatch作為引數。我們使用redux-thunk呼叫API apiservice,並等待收到響應。一旦接收到響應,我們就使用payload 派發一個action

import apiservice from '../services/apiservice';

export function sendEmail(data) {
    return { type:"SEND_EMAIL", payload: data };
}

export function sendEmailAPI(email) {
    return function(dispatch) {
        return apiservice.callAPI(email).then(data => {
            dispatch(sendEmail(data));
        });
    }
}

總結

要想有把握的面試,必須充分了解上述所有主題。 即使你目前正在使用React,理解這些概念也能增強你