摘要: 問題很詳細(xì)经磅,插圖很好看泌绣。
Fundebug經(jīng)授權(quán)轉(zhuǎn)載预厌,版權(quán)歸原作者所有阿迈。
React是流行的javascript框架之一,在2019年及以后將會(huì)更加流行轧叽。React于2013年首次發(fā)布苗沧,多年來廣受歡迎。它是一個(gè)聲明性的炭晒、基于組件的待逞、用于構(gòu)建用戶界面的高效javascript庫。
以下是面試前必須了解的話題网严。
- 什么是聲明式編程
- 聲明式編程 vs 命令式編程
- 什么是函數(shù)式編程
- 什么是組件設(shè)計(jì)模式
- React 是什么
- React 和 Angular 有什么不同
- 什么是虛擬DOM及其工作原理
- 什么是JSX
- 組件和不同類型
- Props 和 State
- 什么是 PropTypes
- 如何更新狀態(tài)和不更新狀態(tài)
- 組件生命周期方法
- 超越繼承的組合
- 如何在React中應(yīng)用樣式
- 什么是Redux及其工作原理
- 什么是React路由器及其工作原理
- 什么是錯(cuò)誤邊界
- 什么是 Fragments
- 什么是傳送門(Portals)
- 什么是 Context
- 什么是 Hooks
- 如何提高性能
- 如何在重新加載頁面時(shí)保留數(shù)據(jù)
- 如何從React中調(diào)用API
- 總結(jié)
什么是聲明式編程
聲明式編程是一種編程范式识樱,它關(guān)注的是你要做什么,而不是如何做震束。它表達(dá)邏輯而不顯式地定義步驟怜庸。這意味著我們需要根據(jù)邏輯的計(jì)算來聲明要顯示的組件。它沒有描述控制流步驟垢村。聲明式編程的例子有HTML割疾、SQL等
HTML file
// HTML
<div>
<p>Declarative Programming</p>
</div>
SQL file
select * from studens where firstName = 'declarative';
聲明式編程 vs 命令式編程
聲明式編程的編寫方式描述了應(yīng)該做什么,而命令式編程描述了如何做嘉栓。在聲明式編程中宏榕,讓編譯器決定如何做事情。聲明性程序很容易推理侵佃,因?yàn)榇a本身描述了它在做什么麻昼。
下面是一個(gè)例子,數(shù)組中的每個(gè)元素都乘以 2
趣钱,我們使用聲明式map
函數(shù)涌献,讓編譯器來完成其余的工作,而使用命令式首有,需要編寫所有的流程步驟燕垃。
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)
什么是函數(shù)式編程
函數(shù)式編程是聲明式編程的一部分。javascript中的函數(shù)是第一類公民井联,這意味著函數(shù)是數(shù)據(jù)卜壕,你可以像保存變量一樣在應(yīng)用程序中保存、檢索和傳遞這些函數(shù)烙常。
函數(shù)式編程有些核心的概念轴捎,如下:
- 不可變性(Immutability)
- 純函數(shù)(Pure Functions)
- 數(shù)據(jù)轉(zhuǎn)換(Data Transformations)
- 高階函數(shù) (Higher-Order Functions)
- 遞歸
- 組合
不可變性(Immutability)
不可變性意味著不可改變鹤盒。 在函數(shù)式編程中,你無法更改數(shù)據(jù)侦副,也不能更改侦锯。 如果要改變或更改數(shù)據(jù),則必須復(fù)制數(shù)據(jù)副本來更改秦驯。
例如尺碰,這是一個(gè)student對象和changeName
函數(shù),如果要更改學(xué)生的名稱译隘,則需要先復(fù)制 student 對象亲桥,然后返回新對象。
在javascript中固耘,函數(shù)參數(shù)是對實(shí)際數(shù)據(jù)的引用题篷,你不應(yīng)該使用 student.firstName =“testing11”,這會(huì)改變實(shí)際的student
對象厅目,應(yīng)該使用Object.assign復(fù)制對象并返回新對象番枚。
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);
純函數(shù)
純函數(shù)是始終接受一個(gè)或多個(gè)參數(shù)并計(jì)算參數(shù)并返回?cái)?shù)據(jù)或函數(shù)的函數(shù)。 它沒有副作用璧瞬,例如設(shè)置全局狀態(tài)户辫,更改應(yīng)用程序狀態(tài),它總是將參數(shù)視為不可變數(shù)據(jù)嗤锉。
我想使用 appendAddress 的函數(shù)向student
對象添加一個(gè)地址渔欢。 如果使用非純函數(shù),它沒有參數(shù)瘟忱,直接更改 student
對象來更改全局狀態(tài)奥额。
使用純函數(shù),它接受參數(shù)访诱,基于參數(shù)計(jì)算垫挨,返回一個(gè)新對象而不修改參數(shù)。
let student = {
firstName: "testing",
lastName: "testing",
marks: 500
}
// 非純函數(shù)
function appendAddress() {
student.address = {streetNumber:"0000", streetName: "first", city:"somecity"};
}
console.log(appendAddress());
// 純函數(shù)
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);
數(shù)據(jù)轉(zhuǎn)換
我們講了很多關(guān)于不可變性的內(nèi)容触菜,如果數(shù)據(jù)是不可變的九榔,我們?nèi)绾胃淖償?shù)據(jù)。如上所述涡相,我們總是生成原始數(shù)據(jù)的轉(zhuǎn)換副本哲泊,而不是直接更改原始數(shù)據(jù)。
再介紹一些 javascript內(nèi)置函數(shù)催蝗,當(dāng)然還有很多其他的函數(shù)切威,這里有一些例子。所有這些函數(shù)都不改變現(xiàn)有的數(shù)據(jù)丙号,而是返回新的數(shù)組或?qū)ο蟆?/p>
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' ]
高階函數(shù)
高階函數(shù)是將函數(shù)作為參數(shù)或返回函數(shù)的函數(shù)先朦,或者有時(shí)它們都有缰冤。 這些高階函數(shù)可以操縱其他函數(shù)。
Array.map喳魏,Array.filter和Array.reduce
是高階函數(shù)棉浸,因?yàn)樗鼈儗⒑瘮?shù)作為參數(shù)。
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
下面是另一個(gè)名為isPersonOld
的高階函數(shù)示例截酷,該函數(shù)接受另外兩個(gè)函數(shù)涮拗,分別是 message
和isYoung
乾戏。
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
遞歸
遞歸是一種函數(shù)在滿足一定條件之前調(diào)用自身的技術(shù)迂苛。只要可能,最好使用遞歸而不是循環(huán)鼓择。你必須注意這一點(diǎn)三幻,瀏覽器不能處理太多遞歸和拋出錯(cuò)誤。
下面是一個(gè)演示遞歸的例子呐能,在這個(gè)遞歸中念搬,打印一個(gè)類似于樓梯的名稱。我們也可以使用for
循環(huán)摆出,但只要可能朗徊,我們更喜歡遞歸。
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中偎漫,我們將功能劃分為小型可重用的純函數(shù)爷恳,我們必須將所有這些可重用的函數(shù)放在一起,最終使其成為產(chǎn)品象踊。 將所有較小的函數(shù)組合成更大的函數(shù)温亲,最終,得到一個(gè)應(yīng)用程序杯矩,這稱為組合栈虚。
實(shí)現(xiàn)組合有許多不同方法。 我們從Javascript中了解到的一種常見方法是鏈接史隆。 鏈接是一種使用點(diǎn)表示法調(diào)用前一個(gè)函數(shù)的返回值的函數(shù)的方法魂务。
這是一個(gè)例子。 我們有一個(gè)name
泌射,如果firstName
和lastName
大于5個(gè)單詞的大寫字母粘姜,剛返回,并且打印名稱的名稱和長度魄幕。
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中相艇,我們使用了不同于鏈接的方法,因?yàn)槿绻?0個(gè)這樣的函數(shù)纯陨,就很難進(jìn)行鏈接坛芽。這里的目的是將所有更簡單的函數(shù)組合起來生成一個(gè)更高階的函數(shù)留储。
const name = compose(
splitmyName,
countEachName,
comvertUpperCase,
returnName
)
console.log(name);
什么是 React
React是一個(gè)簡單的javascript UI庫,用于構(gòu)建高效咙轩、快速的用戶界面获讳。它是一個(gè)輕量級庫,因此很受歡迎活喊。它遵循組件設(shè)計(jì)模式丐膝、聲明式編程范式和函數(shù)式編程概念,以使前端應(yīng)用程序更高效钾菊。它使用虛擬DOM來有效地操作DOM帅矗。它遵循從高階組件到低階組件的單向數(shù)據(jù)流。
React 與 Angular 有何不同煞烫?
Angular是一個(gè)成熟的MVC框架浑此,帶有很多特定的特性,比如服務(wù)滞详、指令凛俱、模板、模塊料饥、解析器等等蒲犬。React是一個(gè)非常輕量級的庫,它只關(guān)注MVC的視圖部分岸啡。
Angular遵循兩個(gè)方向的數(shù)據(jù)流原叮,而React遵循從上到下的單向數(shù)據(jù)流。React在開發(fā)特性時(shí)給了開發(fā)人員很大的自由凰狞,例如篇裁,調(diào)用API的方式、路由等等赡若。我們不需要包括路由器庫达布,除非我們需要它在我們的項(xiàng)目。
什么是Virtual DOM及其工作原理
React 使用 Virtual DOM 來更新真正的 DOM逾冬,從而提高效率和速度黍聂。 我們來詳細(xì)討論這些。
什么是Virtual DOM
瀏覽器遵循HTML指令來構(gòu)造文檔對象模型(DOM)身腻。當(dāng)瀏覽器加載HTML并呈現(xiàn)用戶界面時(shí)产还,HTML文檔中的所有元素都變成DOM元素。
DOM是從根元素開始的元素層次結(jié)構(gòu)嘀趟。例如脐区,看看下面的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>
當(dāng)在瀏覽器中加載這個(gè)HTML時(shí)她按,所有這些HTML元素都被轉(zhuǎn)換成DOM元素牛隅,如下所示
當(dāng)涉及到SPA應(yīng)用程序時(shí)炕柔,首次加載index.html,并在index.html本身中加載更新后的數(shù)據(jù)或另一個(gè)html媒佣。當(dāng)用戶瀏覽站點(diǎn)時(shí)匕累,我們使用新內(nèi)容更新相同的index.html。每當(dāng)DOM發(fā)生更改時(shí)默伍,瀏覽器都需要重新計(jì)算CSS欢嘿、進(jìn)行布局并重新繪制web頁面。
React 使用 Virtual DOM 有效地重建 DOM也糊。 對于我們來說炼蹦,這使得DOM操作的一項(xiàng)非常復(fù)雜和耗時(shí)的任務(wù)變得更加容易。 React從開發(fā)人員那里抽象出所有這些显设,以便在Virtual DOM的幫助下構(gòu)建高效的UI框弛。
虛擬DOM是如何工作的
虛擬DOM只不過是真實(shí) DOM 的 javascript對象表示。 與更新真實(shí) DOM 相比捕捂,更新 javascript 對象更容易,更快捷斗搞。 考慮到這一點(diǎn)指攒,讓我們看看它是如何工作的。
React將整個(gè)DOM副本保存為虛擬DOM
每當(dāng)有更新時(shí)僻焚,它都會(huì)維護(hù)兩個(gè)虛擬DOM允悦,以比較之前的狀態(tài)和當(dāng)前狀態(tài),并確定哪些對象已被更改虑啤。 例如隙弛,段落文本更改為更改。
現(xiàn)在狞山,它通過比較兩個(gè)虛擬DOM 差異全闷,并將這些變化更新到實(shí)際DOM
一旦真正的DOM更新,它也會(huì)更新UI
什么是 JSX
JSX是javascript的語法擴(kuò)展萍启。它就像一個(gè)擁有javascript全部功能的模板語言总珠。它生成React元素,這些元素將在DOM中呈現(xiàn)勘纯。React建議在組件使用JSX局服。在JSX中,我們結(jié)合了javascript和HTML驳遵,并生成了可以在DOM中呈現(xiàn)的react元素淫奔。
下面是JSX的一個(gè)例子。我們可以看到如何將javascript和HTML結(jié)合起來堤结。如果HTML中包含任何動(dòng)態(tài)變量唆迁,我們應(yīng)該使用表達(dá)式{}
佳鳖。
import React from 'react';
export const Header = () => {
const heading = 'TODO App'
return(
<div style={{backgroundColor:'orange'}}>
<h1>{heading}</h1>
</div>
)
}
代碼部署后可能存在的BUG沒法實(shí)時(shí)知道,事后為了解決這些BUG媒惕,花了大量的時(shí)間進(jìn)行l(wèi)og 調(diào)試系吩,這邊順便給大家推薦一個(gè)好用的BUG監(jiān)控工具 Fundebug。
組件和不同類型
React 中一切都是組件妒蔚。 我們通常將應(yīng)用程序的整個(gè)邏輯分解為小的單個(gè)部分穿挨。 我們將每個(gè)單獨(dú)的部分稱為組件。 通常肴盏,組件是一個(gè)javascript函數(shù)科盛,它接受輸入,處理它并返回在UI中呈現(xiàn)的React元素菜皂。
在React中有不同類型的組件贞绵。讓我們詳細(xì)看看。
函數(shù)/無狀態(tài)/展示組件
函數(shù)或無狀態(tài)組件是一個(gè)純函數(shù)恍飘,它可接受接受參數(shù)榨崩,并返回react元素。這些都是沒有任何副作用的純函數(shù)章母。這些組件沒有狀態(tài)或生命周期方法母蛛,這里有一個(gè)例子。
import React from 'react';
import Jumbotron from 'react-bootstrap/Jumbotron';
export const Header = () => {
return(
<Jumbotron style={{backgroundColor:'orange'}}>
<h1>TODO App</h1>
</Jumbotron>
)
}
類/有狀態(tài)組件
類或有狀態(tài)組件具有狀態(tài)和生命周期方可能通過setState()
方法更改組件的狀態(tài)乳怎。類組件是通過擴(kuò)展React創(chuàng)建的彩郊。它在構(gòu)造函數(shù)中初始化,也可能有子組件,這里有一個(gè)例子蚪缀。
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 中處理輸入表單的一種技術(shù)秫逝。表單元素通常維護(hù)它們自己的狀態(tài),而react則在組件的狀態(tài)屬性中維護(hù)狀態(tài)询枚。我們可以將兩者結(jié)合起來控制輸入表單违帆。這稱為受控組件。因此哩盲,在受控組件表單中前方,數(shù)據(jù)由React組件處理。
這里有一個(gè)例子廉油。當(dāng)用戶在 todo 項(xiàng)中輸入名稱時(shí)惠险,調(diào)用一個(gè)javascript函數(shù)handleChange
捕捉每個(gè)輸入的數(shù)據(jù)并將其放入狀態(tài),這樣就在 handleSubmit
中的使用數(shù)據(jù)抒线。
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>
);
}
}
非受控組件
大多數(shù)情況下班巩,建議使用受控組件。有一種稱為非受控組件的方法可以通過使用Ref
來處理表單數(shù)據(jù)。在非受控組件中抱慌,Ref
用于直接從DOM
訪問表單值逊桦,而不是事件處理程序。
我們使用Ref
構(gòu)建了相同的表單抑进,而不是使用React狀態(tài)强经。 我們使用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>
);
}
}
容器組件
容器組件是處理獲取數(shù)據(jù)寺渗、訂閱 redux 存儲(chǔ)等的組件匿情。它們包含展示組件和其他容器組件,但是里面從來沒有html信殊。
高階組件
高階組件是將組件作為參數(shù)并生成另一個(gè)組件的組件炬称。 Redux connect
是高階組件的示例。 這是一種用于生成可重用組件的強(qiáng)大技術(shù)涡拘。
Props 和 State
Props 是只讀屬性玲躯,傳遞給組件以呈現(xiàn)UI和狀態(tài),我們可以隨時(shí)間更改組件的輸出鳄乏。
下面是一個(gè)類組件的示例跷车,它在構(gòu)造函數(shù)中定義了props
和state
,每當(dāng)使用this.setState()
修改狀態(tài)時(shí)汞窗,將再次調(diào)用 render( )
函數(shù)來更改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
隨著時(shí)間的推移,應(yīng)用程序會(huì)變得越來越大仲吏,因此類型檢查非常重要。PropTypes
為組件提供類型檢查蝌焚,并為其他開發(fā)人員提供很好的文檔裹唆。如果react項(xiàng)目不使用 Typescript,建議為組件添加 PropTypes
只洒。
如果組件沒有收到任何 props许帐,我們還可以為每個(gè)組件定義要顯示的默認(rèn) props。這里有一個(gè)例子毕谴。UserDisplay
有三個(gè) prop:name
成畦、address
和age
,我們正在為它們定義默認(rèn)的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
}
如何更新狀態(tài)以及如何不更新
你不應(yīng)該直接修改狀態(tài)循帐。可以在構(gòu)造函數(shù)中定義狀態(tài)值舀武。直接使用狀態(tài)不會(huì)觸發(fā)重新渲染拄养。React 使用this.setState()
時(shí)合并狀態(tài)。
// 錯(cuò)誤方式
this.state.name = "some name"
// 正確方式
this.setState({name:"some name"})
使用this.setState()
的第二種形式總是更安全的银舱,因?yàn)楦碌膒rops和狀態(tài)是異步的瘪匿。這里跛梗,我們根據(jù)這些 props 更新狀態(tài)。
// 錯(cuò)誤方式
this.setState({
timesVisited: this.state.timesVisited + this.props.count
})
// 正確方式
this.setState((state, props) => {
timesVisited: state.timesVisited + props.count
});
組件生命周期方法
組件在進(jìn)入和離開DOM時(shí)要經(jīng)歷一系列生命周期方法棋弥,下面是這些生命周期方法核偿。
componentWillMount()
在渲染前調(diào)用,在客戶端也在服務(wù)端,它只發(fā)生一次顽染。
componentDidMount()
在第一次渲染后調(diào)用漾岳,只在客戶端。之后組件已經(jīng)生成了對應(yīng)的DOM結(jié)構(gòu)家乘,可以通過this.getDOMNode()
來進(jìn)行訪問蝗羊。 如果你想和其他JavaScript框架一起使用,可以在這個(gè)方法中調(diào)用setTimeout
, setInterval
或者發(fā)送AJAX請求等操作(防止異部操作阻塞UI)仁锯。
componentWillReceiveProps()
在組件接收到一個(gè)新的 prop (更新后)時(shí)被調(diào)用耀找。這個(gè)方法在初始化render時(shí)不會(huì)被調(diào)用。
shouldComponentUpdate()
返回一個(gè)布爾值业崖。在組件接收到新的props
或者state
時(shí)被調(diào)用野芒。在初始化時(shí)或者使用forceUpdate
時(shí)不被調(diào)用。 可以在你確認(rèn)不需要更新組件時(shí)使用双炕。
componentWillUpdate()
在組件接收到新的props
或者state
但還沒有render
時(shí)被調(diào)用狞悲。在初始化時(shí)不會(huì)被調(diào)用。
componentDidUpdate()
在組件完成更新后立即調(diào)用妇斤。在初始化時(shí)不會(huì)被調(diào)用摇锋。
componentWillUnMount()
件從 DOM 中移除的時(shí)候立刻被調(diào)用。
getDerivedStateFromError()
這個(gè)生命周期方法在ErrorBoundary類中使用站超。實(shí)際上荸恕,如果使用這個(gè)生命周期方法,任何類都會(huì)變成ErrorBoundary
死相。這用于在組件樹中出現(xiàn)錯(cuò)誤時(shí)呈現(xiàn)回退UI融求,而不是在屏幕上顯示一些奇怪的錯(cuò)誤。
componentDidCatch()
這個(gè)生命周期方法在ErrorBoundary類中使用算撮。實(shí)際上生宛,如果使用這個(gè)生命周期方法,任何類都會(huì)變成ErrorBoundary肮柜。這用于在組件樹中出現(xiàn)錯(cuò)誤時(shí)記錄錯(cuò)誤陷舅。
超越繼承的組合
在React中,我們總是使用組合而不是繼承素挽。我們已經(jīng)在函數(shù)式編程部分討論了什么是組合蔑赘。這是一種結(jié)合簡單的可重用函數(shù)來生成高階組件的技術(shù)。下面是一個(gè)組合的例子,我們在 dashboard 組件中使用兩個(gè)小組件todoForm
和todoList
缩赛。
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中應(yīng)用樣式
將樣式應(yīng)用于React組件有三種方法耙箍。
外部樣式表
在此方法中,你可以將外部樣式表導(dǎo)入到組件使用類中酥馍。 但是你應(yīng)該使用className
而不是class
來為React元素應(yīng)用樣式, 這里有一個(gè)例子辩昆。
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;
內(nèi)聯(lián)樣式
在這個(gè)方法中,我們可以直接將 props
傳遞給HTML元素旨袒,屬性為style
帮碰。這里有一個(gè)例子哥遮。這里需要注意的重要一點(diǎn)是渡处,我們將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>
)
}
定義樣式對象并使用它
因?yàn)槲覀儗avascript對象傳遞給style
屬性必孤,所以我們可以在組件中定義一個(gè)style
對象并使用它猾骡。下面是一個(gè)示例,你也可以將此對象作為 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的一個(gè)狀態(tài)管理庫兴想,它基于flux。 Redux簡化了React中的單向數(shù)據(jù)流赡勘。 Redux將狀態(tài)管理完全從React中抽象出來嫂便。
它是如何工作的
在React中,組件連接到 redux 闸与,如果要訪問 redux毙替,需要派出一個(gè)包含 id
和負(fù)載(payload) 的 action
。action 中的 payload
是可選的践樱,action 將其轉(zhuǎn)發(fā)給 Reducer蔚龙。
當(dāng)reducer
收到action
時(shí),通過 swithc...case
語法比較 action
中type
映胁。 匹配時(shí),更新對應(yīng)的內(nèi)容返回新的 state
甲雅。
當(dāng)Redux
狀態(tài)更改時(shí)解孙,連接到Redux
的組件將接收新的狀態(tài)作為props
。當(dāng)組件接收到這些props
時(shí)抛人,它將進(jìn)入更新階段并重新渲染 UI弛姜。
Redux 循環(huán)細(xì)節(jié)
讓我們詳細(xì)看看整個(gè)redux 循環(huán)細(xì)節(jié)。
Action: Action 只是一個(gè)簡單的json對象妖枚,type 和有payload作為鍵廷臼。type 是必須要有的,payload是可選的。下面是一個(gè) action 的例子荠商。
// action
{
type:"SEND_EMAIL",
payload: data
};
Action Creators:這些是創(chuàng)建Actions
的函數(shù)寂恬,因此我們在派發(fā)action
時(shí)不必在組件中手動(dòng)編寫每個(gè) action
。 以下是 action creator 的示例莱没。
// action creator
export function sendEamil(data) {
return { type:"SEND_EMAIL", payload: data};
}
Reducers:Reducers 是純函數(shù)初肉,它將 action
和當(dāng)前 state
作為參數(shù),計(jì)算必要的邏輯并返回一個(gè)新r的state
。 這些 Reducers 沒有任何副作用饰躲。 它不會(huì)改變 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
進(jìn)行連接
mapStateToProps:此函數(shù)將state
映射到 props
上,因此只要state
發(fā)生變化嘹裂,新 state 會(huì)重新映射到 props
妄壶。 這是訂閱store
的方式。
mapDispatchToProps:此函數(shù)用于將 action creators
綁定到你的props
寄狼。以便我們可以在第12
行中使用This . props.actions.sendemail()
來派發(fā)一個(gè)動(dòng)作丁寄。
connect
和bindActionCreators
來自 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
是應(yīng)用程序中路由的庫奢浑。 React庫中沒有路由功能,需要單獨(dú)安裝react-router-dom
腋腮。
react-router-dom 提供兩個(gè)路由器BrowserRouter
和HashRoauter
雀彼。前者基于rul的pathname段,后者基于hash段即寡。
前者:http://127.0.0.1:3000/article/num1
后者:http://127.0.0.1:3000/#/article/num1(不一定是這樣徊哑,但#是少不了的)
react-router-dom 組件
-
BrowserRouter
和HashRouter
是路由器。 -
Route
用于路由匹配聪富。 -
Link
組件用于在應(yīng)用程序中創(chuàng)建鏈接莺丑。 它將在HTML中渲染為錨標(biāo)記。 -
NavLink
是突出顯示當(dāng)前活動(dòng)鏈接的特殊鏈接墩蔓。 -
Switch
不是必需的梢莽,但在組合路由時(shí)很有用。 -
Redirect
用于強(qiáng)制路由重定向
下面是組件中的Link
奸披、NavLink
和Redirect
的例子
// 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 組件的示例昏名。 如果你查看下面的示例,我們將匹配路徑并使用Switch
和Route
呈現(xiàn)相應(yīng)的組件阵面。
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
什么是錯(cuò)誤邊界
在 React 中轻局,我們通常有一個(gè)組件樹洪鸭。如果任何一個(gè)組件發(fā)生錯(cuò)誤,它將破壞整個(gè)組件樹仑扑。沒有辦法捕捉這些錯(cuò)誤览爵,我們可以用錯(cuò)誤邊界優(yōu)雅地處理這些錯(cuò)誤。
錯(cuò)誤邊界有兩個(gè)作用
- 如果發(fā)生錯(cuò)誤夫壁,顯示回退UI
- 記錄錯(cuò)誤
下面是ErrorBoundary
類的一個(gè)例子拾枣。如果類實(shí)現(xiàn)了 getDerivedStateFromError
或componentDidCatch
這兩個(gè)生命周期方法的任何一下,盒让,那么這個(gè)類就會(huì)成為ErrorBoundary梅肤。前者返回{hasError: true}
來呈現(xiàn)回退UI,后者用于記錄錯(cuò)誤邑茄。
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;
}
}
以下是我們?nèi)绾卧谄渲幸粋€(gè)組件中使用ErrorBoundary姨蝴。使用ErrorBoundary類包裹 ToDoForm
和ToDoList
。 如果這些組件中發(fā)生任何錯(cuò)誤肺缕,我們會(huì)記錄錯(cuò)誤并顯示回退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中,我們需要有一個(gè)父元素同木,同時(shí)從組件返回React元素浮梢。有時(shí)在DOM中添加額外的節(jié)點(diǎn)會(huì)很煩人。使用 Fragments彤路,我們不需要在DOM中添加額外的節(jié)點(diǎn)秕硝。我們只需要用 React.Fragment
或才簡寫 <>
來包裹內(nèi)容就行了。如下 所示:
// 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)
默認(rèn)情況下洲尊,所有子組件都在UI上呈現(xiàn)远豺,具體取決于組件層次結(jié)構(gòu)。Portal 提供了一種將子節(jié)點(diǎn)渲染到存在于父組件以外的 DOM 節(jié)點(diǎn)的優(yōu)秀的方案坞嘀。
這里有一個(gè)例子躯护。默認(rèn)情況下,父組件在DOM層次結(jié)構(gòu)中有子組件丽涩。
我們可以將 children
組件移出parent
組件并將其附加 id 為 someid
的 Dom 節(jié)點(diǎn)下棺滞。
首先,獲取 id 為 someid矢渊,我們在constrcutorand中創(chuàng)建一個(gè)元素div检眯,將child附加到componentDidMount中的someRoot。 最后昆淡,我們在ReactDOM.createPortal(this.props.childen),domnode的幫助下將子節(jié)點(diǎn)傳遞給該特定DOM節(jié)點(diǎn)刽严。
首先昂灵,先獲取 id 為someid
DOM元素避凝,接著在構(gòu)造函數(shù)中創(chuàng)建一個(gè)元素div,在 componentDidMount
方法中將 someRoot
放到 div 中 眨补。 最后管削,通過
ReactDOM.createPortal(this.props.childen), domnode)
將 children
傳遞到對應(yīng)的節(jié)點(diǎn)下。
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,
);
}
}
什么是上下文
有時(shí)我們必須將props
傳遞給組件樹撑螺,即使所有中間組件都不需要這些props
含思。上下文是一種傳遞props
的方法,而不用在每一層傳遞組件樹甘晤。
什么是 Hooks
Hooks 是React版本16.8中的新功能含潘。 請記住,我們不能在函數(shù)組件中使用state
线婚,因?yàn)樗鼈儾皇穷惤M件遏弱。Hooks 讓我們在函數(shù)組件中可以使用state 和其他功能。
目前沒有重大變化塞弊,我們不必放棄類組件漱逸。
Hook 不會(huì)影響你對 React 概念的理解。 恰恰相反游沿,Hook 為已知的 React 概念提供了更直接的 API:props饰抒, state,context诀黍,refs 以及生命周期袋坑。稍后我們將看到,Hook 還提供了一種更強(qiáng)大的方式來組合他們蔗草。
我們可以使用一些鉤子咒彤,例如useState,useEffect咒精,useContext镶柱,useReducer等。
下面是 Hooks 的基本規(guī)則
- Hooks 應(yīng)該在外層使用模叙,不應(yīng)該在循環(huán)歇拆,條件或嵌套函數(shù)中使用
- Hooks 應(yīng)該只在函數(shù)組件中使用。
讓我們看一個(gè)例子來理解 hooks范咨。 這是一個(gè)函數(shù)組件故觅,它采用props
并在UI上顯示這些props
。 在useState
鉤子的幫助下渠啊,我們將這個(gè)函數(shù)組件轉(zhuǎn)換為有狀態(tài)組件输吏。 首先,我們在第5行定義狀態(tài)替蛉,這相當(dāng)于
constructor(props) {
super(props);
this.state = {
name:'myname', age:10, address:'0000 one street'
}
}
useState
返回兩個(gè)項(xiàng)贯溅,一個(gè)是user
拄氯,另一個(gè)是setUser
函數(shù)。 user
是一個(gè)可以在沒有 this
關(guān)鍵字的情況下直接使用的對象它浅,setUser
是一個(gè)可以用來設(shè)置用戶點(diǎn)擊第21
行按鈕的狀態(tài)的函數(shù)译柏,該函數(shù)等效于以下內(nèi)容。
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>
</>
);
};
如何提高性能
我們可以通過多種方式提高應(yīng)用性能姐霍,以下這些比較重要:
- 適當(dāng)?shù)厥褂?code>shouldComponentUpdate生命周期方法鄙麦。 它避免了子組件的不必要的渲染。 如果樹中有100個(gè)組件镊折,則不重新渲染整個(gè)組件樹來提高應(yīng)用程序性能胯府。
- 使用
create-react-app
來構(gòu)建項(xiàng)目,這會(huì)創(chuàng)建整個(gè)項(xiàng)目結(jié)構(gòu)腌乡,并進(jìn)行大量優(yōu)化盟劫。 - 不可變性是提高性能的關(guān)鍵。不要對數(shù)據(jù)進(jìn)行修改与纽,而是始終在現(xiàn)有集合的基礎(chǔ)上創(chuàng)建新的集合侣签,以保持盡可能少的復(fù)制,從而提高性能急迂。
- 在顯示列表或表格時(shí)始終使用
Keys
影所,這會(huì)讓 React 的更新速度更快 - 代碼分離是將代碼插入到單獨(dú)的文件中,只加載模塊或部分所需的文件的技術(shù)僚碎。
如何在重新加載頁面時(shí)保留數(shù)據(jù)
單頁應(yīng)用程序首先在DOM中加載index.html
猴娩,然后在用戶瀏覽頁面時(shí)加載內(nèi)容,或者從同一index.html
中的后端API獲取任何數(shù)據(jù)勺阐。
如果通過點(diǎn)擊瀏覽器中的重新加載按鈕重新加載頁面index.html
卷中,整個(gè)React應(yīng)用程序?qū)⒅匦录虞d,我們將丟失應(yīng)用程序的狀態(tài)渊抽。 如何保留應(yīng)用狀態(tài)蟆豫?
每當(dāng)重新加載應(yīng)用程序時(shí),我們使用瀏覽器localstorage
來保存應(yīng)用程序的狀態(tài)懒闷。我們將整個(gè)存儲(chǔ)數(shù)據(jù)保存在localstorage
中十减,每當(dāng)有頁面刷新或重新加載時(shí),我們從localstorage
加載狀態(tài)愤估。
如何在React進(jìn)行API調(diào)用
我們使用redux-thunk
在React中調(diào)用API帮辟。因?yàn)?code>reduce是純函數(shù),所以沒有副作用玩焰,比如調(diào)用API由驹。
因此,我們必須使用redux-thunk
從 action creators 那里進(jìn)行 API 調(diào)用昔园。Action creator 派發(fā)一個(gè)action荔棉,將來自API的數(shù)據(jù)放入action 的 payload
中闹炉。Reducers 接收我們在上面的redux
循環(huán)中討論的數(shù)據(jù),其余的過程也是相同的润樱。
redux-thunk是一個(gè)中間件。一旦它被引入到項(xiàng)目中羡棵,每次派發(fā)一個(gè)action
時(shí)壹若,都會(huì)通過thunk
傳遞。如果它是一個(gè)函數(shù)皂冰,它只是等待函數(shù)處理并返回響應(yīng)店展。如果它不是一個(gè)函數(shù),它只是正常處理秃流。
這里有一個(gè)例子赂蕴。sendEmailAPI
是從組件中調(diào)用的函數(shù),它接受一個(gè)數(shù)據(jù)并返回一個(gè)函數(shù)舶胀,其中dispatch
作為參數(shù)概说。我們使用redux-thunk
調(diào)用API apiservice
,并等待收到響應(yīng)嚣伐。一旦接收到響應(yīng)糖赔,我們就使用payload
派發(fā)一個(gè)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));
});
}
}
總結(jié)
要想有把握的面試轩端,必須充分了解上述所有主題放典。 即使你目前正在使用React,理解這些概念也能增強(qiáng)你在職場中信心基茵。