- 什么是聲明式編程
- 聲明式編程 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
的高階函數示例,該函數接受另外兩個函數,分別是 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
復制代碼
遞歸
遞歸是一種函數在滿足一定條件之前調用自身的技術。只要可能,最好使用遞歸而不是循環。你必須注意這一點,瀏覽器不能處理太多遞歸和拋出錯誤。
下面是一個演示遞歸的例子,在這個遞歸中,打印一個類似于樓梯的名稱。我們也可以使用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
,如果firstName
和lastName
大于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元素,如下所示
[圖片上傳失敗...(image-1a52f9-1629101283194)]
<figcaption></figcaption>
當涉及到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
[圖片上傳失敗...(image-d76bf3-1629101283194)]
<figcaption></figcaption>
每當有更新時,它都會維護兩個虛擬DOM,以比較之前的狀態和當前狀態,并確定哪些對象已被更改。 例如,段落文本更改為更改。
[圖片上傳失敗...(image-744d26-1629101283194)]
<figcaption></figcaption>
現在,它通過比較兩個虛擬DOM 差異,并將這些變化更新到實際DOM
[圖片上傳失敗...(image-425f0c-1629101283194)]
<figcaption></figcaption>
一旦真正的DOM更新,它也會更新UI
[圖片上傳失敗...(image-7c4648-1629101283194)]
<figcaption></figcaption>
什么是 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和狀態,我們可以隨時間更改組件的輸出。
下面是一個類組件的示例,它在構造函數中定義了props
和state
,每當使用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:name
、address
和age
,我們正在為它們定義默認的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 組件中使用兩個小組件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中應用樣式
將樣式應用于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
語法比較 action
中type
。 匹配時,更新對應的內容返回新的 state
。
當Redux
狀態更改時,連接到Redux
的組件將接收新的狀態作為props
。當組件接收到這些props
時,它將進入更新階段并重新渲染 UI。
[圖片上傳失敗...(image-48ef9e-1629101283193)]
<figcaption></figcaption>
Redux 循環細節
讓我們詳細看看整個redux 循環細節。
[圖片上傳失敗...(image-348d08-1629101283193)]
<figcaption></figcaption>
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
作為參數,計算必要的邏輯并返回一個新的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()
來派發一個動作。
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
是應用程序中路由的庫。 React庫中沒有路由功能,需要單獨安裝react-router-dom
。
react-router-dom 提供兩個路由器BrowserRouter
和HashRoauter
。前者基于url的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
組件用于在應用程序中創建鏈接。 它將在HTML中渲染為錨標記。 -
NavLink
是突出顯示當前活動鏈接的特殊鏈接。 -
Switch
不是必需的,但在組合路由時很有用。 -
Redirect
用于強制路由重定向
下面是組件中的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
呈現相應的組件。
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
類的一個例子。如果類實現了 getDerivedStateFromError
或componentDidCatch
這兩個生命周期方法的任何一下,,那么這個類就會成為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類包裹 ToDoForm
和ToDoList
。 如果這些組件中發生任何錯誤,我們會記錄錯誤并顯示回退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層次結構中有子組件。
[圖片上傳失敗...(image-a9489-1629101283191)]
<figcaption></figcaption>
我們可以將 children
組件移出parent
組件并將其附加 id 為 someid
的 Dom 節點下。
首先,獲取 id 為 someid,我們在constructor中創建一個元素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,
);
}
}
復制代碼
[圖片上傳失敗...(image-3329bb-1629101283191)]
<figcaption></figcaption>
什么是上下文
有時我們必須將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
加載狀態。
[圖片上傳失敗...(image-c74336-1629101283191)]
<figcaption></figcaption>
如何在React進行API調用
我們使用redux-thunk
在React中調用API。因為reduce
是純函數,所以沒有副作用,比如調用API。
因此,我們必須使用redux-thunk
從 action creators 那里進行 API 調用。Action creator 派發一個action,將來自API的數據放入action 的 payload
中。Reducers 接收我們在上面的redux
循環中討論的數據,其余的過程也是相同的。
[圖片上傳失敗...(image-748015-1629101283191)]
<figcaption></figcaption>
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));
});
}
}
復制代碼