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

阿里雲最近在作活動,低至2折,有興趣能夠看看:
https://promotion.aliyun.com/...

爲了保證的可讀性,本文采用意譯而非直譯。javascript

React是流行的javascript框架之一,在2019年及之後將會更加流行。React於2013年首次發佈,多年來廣受歡迎。它是一個聲明性的、基於組件的、用於構建用戶界面的高效javascript庫。css

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

  • 什麼是聲明式編程
  • 聲明式編程 vs 命令式編程
  • 什麼是函數式編程
  • 什麼是組件設計模式
  • React 是什麼
  • React 和 Angular 有什麼不一樣
  • 什麼是虛擬DOM及其工做原理
  • 什麼是JSX
  • 組件和不一樣類型
  • Props 和 State
  • 什麼是 PropTypes
  • 如何更新狀態和不更新狀態
  • 組件生命週期方法
  • 超越繼承的組合
  • 如何在React中應用樣式
  • 什麼是Redux及其工做原理
  • 什麼是React路由器及其工做原理
  • 什麼是錯誤邊界
  • 什麼是 Fragments
  • 什麼是傳送門(Portals)
  • 什麼是 Context
  • 什麼是 Hooks
  • 如何提升性能
  • 如何在從新加載頁面時保留數據
  • 如何從React中調用API
  • 總結

什麼是聲明式編程

聲明式編程是一種編程範式,它關注的是你要作什麼,而不是如何作。它表達邏輯而不顯式地定義步驟。這意味着咱們須要根據邏輯的計算來聲明要顯示的組件。它沒有描述控制流步驟。聲明式編程的例子有HTML、SQL等前端

HTML filejava

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

SQL filenode

select * from studens where firstName = 'declarative';

聲明式編程 vs 命令式編程

聲明式編程的編寫方式描述了應該作什麼,而命令式編程描述瞭如何作。在聲明式編程中,讓編譯器決定如何作事情。聲明性程序很容易推理,由於代碼自己描述了它在作什麼。react

下面是一個例子,數組中的每一個元素都乘以 2,咱們使用聲明式map函數,讓編譯器來完成其他的工做,而使用命令式,須要編寫全部的流程步驟。git

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中的函數是第一類公民,這意味着函數是數據,你能夠像保存變量同樣在應用程序中保存、檢索和傳遞這些函數。github

函數式編程有些核心的概念,以下:web

  • 不可變性(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元素,以下所示

clipboard.png

當涉及到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

clipboard.png

每當有更新時,它都會維護兩個虛擬DOM,以比較以前的狀態和當前狀態,並肯定哪些對象已被更改。 例如,段落文本更改成更改。

clipboard.png

如今,它經過比較兩個虛擬DOM 差別,並將這些變化更新到實際DOM

clipboard.png

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

clipboard.png

什麼是 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>
    )
}

組件和不一樣類型

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。

clipboard.png

Redux 循環細節

讓咱們詳細看看整個redux 循環細節。

clipboard.png

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層次結構中有子組件。

clipboard.png

咱們能夠將 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,
    );
  }
}

clipboard.png

什麼是上下文

有時咱們必須將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'})


1  import React, { useState } from 'react';
    2
    3  export const UserDisplay = ({name, address, age}) => {
    4
    5    const [user, setUser] = useState({ name: 'myname', age: 10, address: '0000 onestreet' });
    6
    7    return (
    8        <>
    9            <div>
    10                <div class="label">Name:</div>
    11                <div>{user.name}</div>
    12            </div>
    13            <div>
    14                <div class="label">Address:</div>
    15                <div>{user.address}</div>
    16            </div>
    17            <div>
    18              <div class="label">Age:</div>
    19                <div>{user.age}</div>
    20            </div>
    21            <button onClick={() => setUser({name: 'name changed'})}>
    22                Click me
    23            </button>
    24        </>
    25    )
    26 }

如何提升性能

咱們能夠經過多種方式提升應用性能,如下這些比較重要:

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

如何在從新加載頁面時保留數據

單頁應用程序首先在DOM中加載index.html,而後在用戶瀏覽頁面時加載內容,或者從同一index.html中的後端API獲取任何數據。

若是經過點擊瀏覽器中的從新加載按鈕從新加載頁面index.html,整個React應用程序將從新加載,咱們將丟失應用程序的狀態。 如何保留應用狀態?

每當從新加載應用程序時,咱們使用瀏覽器localstorage來保存應用程序的狀態。咱們將整個存儲數據保存在localstorage中,每當有頁面刷新或從新加載時,咱們從localstorage加載狀態。

clipboard.png

如何在React進行API調用

咱們使用redux-thunk在React中調用API。由於reduce是純函數,因此沒有反作用,好比調用API。

所以,咱們必須使用redux-thunk從 action creators 那裏進行 API 調用。Action creator 派發一個action,未來自API的數據放入action 的 payload 中。Reducers 接收咱們在上面的redux循環中討論的數據,其他的過程也是相同的。

clipboard.png

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,理解這些概念也能加強你在職場中信心。

代碼部署後可能存在的BUG無法實時知道,過後爲了解決這些BUG,花了大量的時間進行log 調試,這邊順便給你們推薦一個好用的BUG監控工具 Fundebug

交流

這篇來來回回整理了快五天,你們給個讚唄!

乾貨系列文章彙總以下,以爲不錯點個Star,歡迎 加羣 互相學習。

https://github.com/qq44924588...

我是小智,公衆號「大遷世界」做者,對前端技術保持學習愛好者。我會常常分享本身所學所看的乾貨,在進階的路上,共勉!

關注公衆號,後臺回覆福利,便可看到福利,你懂的。

clipboard.png

相關文章
相關標籤/搜索