一. React developer tools安裝及使用
這是React項目的調試工具,方便我們在開發過程中對React項目進行調試。
安裝步驟:
1. 去github下載react-devtools壓縮包,也可以通過git bash下載:
git clone https://github.com/facebook/react-devtools.git
2. 下載成功後,進入react-devtools文件夾,通過git bash安裝依賴,安裝時間會比較長:
npm --registry https://registry.npm.taobao.org install
3. 安裝成功後,我們便可以打包一份擴展程序出來:
npm run build:extension:chrome
4. 進入react-devtools\shells\chrome\build目錄下,將裏面的unpacked文件夾拖進chrome擴展程序裏面即可。
注意:此方法不太好使,建議使用別的方法。
二. PropTypes與DefaultProps的應用
1. PropTypes,子組件可以對父組件傳過來的屬性或者方法做一個類型校驗,不符合時會警告。
使用方法:
在TodoItem組件中引入PropTypes
在class的後面使用PropTypes來對父組件傳過來的值做類型校驗:
當傳過來的值類型不符合時,控制檯會警告。
知識點:
當我們需要某個屬性必須傳的時候,我們可以這麼寫:
TodoItem.propTypes = {
test: PropTypes.string.isRequired, //此處要求test時必須傳,且是字符串類型
content: PropTypes.string,
deleteItem: PropTypes.func,
index: PropTypes.number
}
2. DefaultProps, 默認值,當我們子組件要求父組件傳某個屬性,但是父組件卻沒有傳,我們可以自己定義一個默認值。
使用方法:
還有一些關於props的功能,可以自行查閱react官網。
三. props,state與render函數的關係
1. React中數據與頁面之間互相聯動的底層運行機理。
當組件的state或者props發生改變的時候,render函數就會重新執行。
- 拿我們前面做的TodoList爲例。當我們往input框輸入內容的時候,onchange事件就會被執行,而onchange事件對應的方法就會改變state,一旦state發生改變,那麼render函數就會重新地被執行,那麼頁面就會重新渲染,渲染的數據也是最新的。我們可以通過在render函數中console.log()內容進行驗證,可以發現每次輸入都會打印。
- props改變的場景:當我們父組件向子組件傳值的時候,子組件通過props接收。父組件向子組件傳遞的值發生了改變,那麼props也就發生了改變,相應地,子組件中的render函數就會重新執行。還有一種情況子組件的render函數也會被重新執行,當父組件的render函數重新執行的時候,由於子組件也是在父組件的render函數中被調用的,所以也會導致子組件被重新渲染,也就是子組件的render函數被重新執行。
四. React中的虛擬DOM
假如我們需要自己實現一個React數據重新渲染的功能,我們需要做如下準備:
1、初始方案
之所以上面的準備耗性能,是因爲我們上面每次都是把整個dom重新生成,而不是隻生成數據改變了的那部分dom,這消耗了性能,另一方面替換掉完整的dom,這也會消耗性能。
2、第一次改進
上面的改進方案並不直接替換全部的原始dom,這節約了一部分性能。但是新的dom和原始dom進行比對又消耗了一部分性能,所以性能的提升並不明顯。
3、React提出了虛擬DOM的方案
上面便是React的虛擬DOM,在這裏需要知道一個點:js創建或者操作對象比創建或者操作真實的dom節省了很大的性能,這就是React提出虛擬DOM的原因。
五. 深入瞭解虛擬DOM
1. 在實際過程中, React的虛擬dom和上面有一些區別,上面是爲了方便用戶理解。實際上是這樣的:
2. 我們項目中render函數裏面的標籤內容不是真實的dom,它只是模板,當render函數執行的時候模板與數據相結合,生成虛擬的dom。
過程: JSX模板-------> createElement-----> 虛擬dom(JS對象)------->真實的dom
原理:
render() {
return (
<div>item</div>
)
}
上面的jsx語法執行以後實際上是調用了下面的方法
render() {
return React.createElement('div', {}, 'item');
}
這兩次代碼在頁面中的效果是一樣的,只不過第二次的代碼調用了更底層的實現方法,沒有使用jsx語法。實際開發中之所以不採用createElement方法,是因爲jsx語法更簡潔,更方便我們編寫。Vue框架中的虛擬dom機制和React是一致的。
3. 虛擬DOM的優點。
- 性能提升了
- 它使得跨端應用得以實現。React Native
React Native之所以能做app,依靠的就是虛擬DOM。當我們運行在瀏覽器端的時候,虛擬DOM就會生成真實的DOM,當我們運行在原生應用平臺的時候,虛擬DOM就會生成相應的原生應用組件。
六. 虛擬DOM中的Diff算法
兩個虛擬DOM比對的方式我們就稱爲Diff算法。
1. 爲什麼React中要把setstate設置成爲異步
比如說我們連續調用了三次setstate,React就會把三次合併成一次setstate,只去做一次虛擬DOM的比對,然後去更新一次dom,這樣就可以省去額外的兩次DOM比對帶來的性能耗費。這麼設置本質上是爲了提高React底層的性能。
2. 爲什麼不建議用index來做key值
React中虛擬DOM比對的Diff算法是同級比對的,當數據發生改變了以後,又會生成一個新的虛擬DOM,兩者進行比對,找到差異以後就會生成新真實的DOM。首先比對最頂層的虛擬DOM,假設他倆一致就會去比對第二層。如果他倆不一致,於是他就會把原始頁面上的虛擬DOM,對應的下面的所有DOM,全部都刪除掉。重新生成一遍節點下的所有的DOM,用重新生成的這些DOM,替換掉原始頁面上的DOM。也就是它如果比對的這層虛擬DOM不一致,下面幾層就不去比了,整個的就對原始的DOM進行替換了。這麼比對有些可重複利用的dom還是會被銷燬掉,消耗了一些性能,但是好處是算法簡單,這樣帶來的好處就是比對的速度會非常的快,大大減少了比對算法的性能上的消耗。
我們平時給數組列表設置key值,使得兩個虛擬DOM之間有名字,方便兩兩進行比對。而我們不推薦使用index值來作爲key值,因爲比如說第二次的虛擬DOM是我們刪除了數組的某一項,此時一部分元素的index值就發生了改變,這樣我們算法根據key值進行比對的就發生了錯誤。這就是爲什麼我們一般不推薦使用index來作爲key值得原因。
七. React中ref的使用
1、希望content的類型可以是字符串或者是數組
2、 ref的使用
在React中除了可以使用e.target來獲取對應的dom元素以外,還可以使用ref來獲取dom元素。
1. 之前的TodoList中handleInputChange方法裏面的e.target可以用ref替換。方法:
首先給render函數裏面的input添加ref(注意:inputDom是一個參數,可以自己隨便定義)
接着將handleInputChange方法裏面的e.target替換即可。實際效果一樣:
但是React是數據驅動的框架,我們不推薦使用ref直接操作dom,除非遇到很複雜的業務必須操作dom。
2、使用ref遇到的一個問題:別忘了setState是異步的。
我們希望在TodoList中實現一個功能,點擊提交按鈕,打印出ul裏面div的長度。
首先:
接着:
我們通過控制檯發現結果不對:每次打印都會少一個。
原因是React的setState方法是異步的,每次打印出來以後setState方法纔可能會執行。解決方法:
使用setState方法的第二個參數,該參數裏面的函數會等setState異步執行完成之後纔會被執行。
結果:
八、React的生命週期鉤子
React的生命週期主要經歷了四個階段:
Initialization: 初始化階段
Mounting:組件掛載到頁面階段
Updating:組件內容更新階段
Unmounting:把組件從頁面去除階段
1、初始化階段:
該階段的生命週期函數在組件剛被創建的那個時刻就會調用。由於constructor這個函數並非React所獨有,ES6中也存在,所以React並沒有將它歸爲生命週期函數,但是它就是在該階段調用的。在該階段會定義state,會接收props。
2、掛載階段:
該階段有三個生命週期函數被調用
- componentWillMount 在組件即將第一次被掛載到頁面的時刻自動執行
- render 組件掛載到頁面的時刻自動執行
- componentDidMount 組件第一次被掛載到頁面之後,自動地執行
我們分別在不同階段打印相應的生命週期函數,結果如下:
我們在輸入框輸入文字,發現只有render函數被調用,這是因爲另外兩個只在第一次被掛載的時候會調用,之後就不會被調用了:
3、組件內容更新階段
有兩種情況會進入該階段,一種是props發生改變,另一種是state發生改變。從上面的圖可以發現兩者的區別是props比state開始多調用一個生命週期函數(因爲歸根結底props改變的原因是因爲父組件的state改變)。
兩者都有的:
- shouldComponentUpdate 組件被更新之前,他會自動被執行。有三種情況
1.
shouldComponentUpdate() {
console.log('shouldComponentUpdate');
}
然後在輸入框中輸入,但是輸入框卻不現實文字,結果:
這是因爲這個生命週期函數最後需要return一個布爾值。
2. return ture
shouldComponentUpdate() {
console.log('shouldComponentUpdate');
return true;
}
爲了方便我們記憶,上面代碼可以這麼理解:應該讓組件更新嗎?return true的話就讓組件更新,相應的輸入框也能顯示文字,組件更新也就能夠調用rener函數了。結果頁面:
3. return false
shouldComponentUpdate() {
console.log('shouldComponentUpdate');
return false;
}
return false組件不會更新,所以輸入框不會顯示我們輸入的文字,但是控制檯不會報錯,因爲我們return 了一個布爾值。結果頁面:
- componentWillUpdate
//組件被更新之前,它會自動執行。但是它在shouldComponentUpdate之後執行
//如果shouldComponentUpdate返回true它纔會執行
//如果返回false,這個函數就不會被執行了
componentWillUpdate() {
console.log('componentWillUpdate');
}
下面是return true的情況:
- render
- componentDidUpdate 組件更新完畢之後,它會被執行
componentDidUpdate() {
console.log('componentDidUpdate')
}
結果:
props獨有的:
- componentWillReceiveProps 前提是一個組件要從父組件接收參數。如果這個組件第一次存在於父組件中不會被執行,如果這個組件已經存在於父組件中了,纔會被執行。
該生命週期函數是props獨有的,它執行的前提是該組件必須有props參數,也就是父組件給該組件傳值。所以我們把這個生命週期函數寫在TodoItem這個子組件中。
效果演示:
TodoItem:
componentWillReceiveProps() {
console.log('child componentWillReceiveProps');
}
結果:我們輸入完畢第一次點擊提交以後,不會打印出來,再次輸入以後,此時父組件的render函數重新執行後,纔會執行該生命週期函數,即打印出來。
4、把組件從頁面去除階段
- componentWillUnmount 當這個組件即將被從頁面剔除的時候執行
TodoItem:
componentWillUnmount() {
console.log('child componentWillUnmount');
}
結果:當我們點擊列表項時,該列表項被刪除,此時該生命週期函數被執行
九、生命週期函數的使用場景
1. 注意: render這個生命週期函數必須存在。
這是因爲React的組件是繼承自Component這個組件的,該組件默認內置了所有的生命週期函數,唯獨沒有內置render這個生命週期函數的實現。
2. 利用生命週期函數提高性能
案例:
TodoItem中的render函數中添加console.log('child render');
結果:
實際中上面每次輸入導致TodoItem這個子組件都會重新被渲染是耗性能的,我們可以利用shouldComponentUpdate這個生命週期函數來實現。
TodoItem:
shouldComponentUpdate(nextProps, nextState) {
//nextProps表示組件如果更新後的Props
//nextState表示組件如果更新後的state
if(nextProps.content !== this.props.content) {
return true;
} else {
return false;
}
}
效果:
可以發現,此時當我們只輸入沒有提交的時候,TodoItem這個組件不會重新被渲染,提高了性能。
3. 在哪個生命週期函數中發送ajax請求?
不要放在render函數中,因爲它在頁面中經常會被反覆地執行。推薦放在componentDidMount這個生命週期函數中。
10、使用 mockjs 實現本地數據 mock
1、課程講的 charles 折騰了一下午也用不了,應該是版本更新後的問題。查找資料後使用 mockjs 成功實現了
- 安裝 mockjs : npm install mockjs ;
- 在 src 目錄下建立 mock 文件夾,裏面建立 mockdata.js 文件:
import Mock from 'mockjs';
Mock.mock('/todolist', {
data: ['a', 'b', 'c']
}) //第一個參數是自己寫的,到時候 axios 也寫這個路徑即可, 第二個參數放數據
- TodoList.js 文件引入 mockdata.js , 然後添加代碼:
componentDidMount() {
axios.get('/todolist')
.then((res) => {
const data = res.data.data;
this.setState(() => ({
list: [...data]
}))
})
.catch(() => {console.log('error')});
}
此時頁面就渲染出模擬的數據了。
11、React中實現CSS過渡動畫
刪除之前的 TodoList 後的目錄結構:
index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(<App />, document.getElementById('root'));
App.js:
import React, { Component, Fragment } from 'react';
import './style.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
show: true
};
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<Fragment>
<div className={this.state.show ? 'show' : 'hide'}>hello world</div>
<button onClick={this.handleToggle}>toggle</button>
</Fragment>
)
}
handleToggle() {
this.setState({
show: this.state.show ? false : true
})
}
}
export default App;
style.css:
.show {
opacity: 1;
transition: all 1s ease-in; /*所有的屬性都用 1s 的時間來切換*/
}
.hide {
opacity: 0;
transition: all 1s ease-in; /*所有的屬性都用 1s 的時間來切換*/
}
啓動項目就實現了一個切換顯示與否的漸變效果。
12、React中使用CSS動畫效果
之前代碼不變,就改變 style.css 的效果:
.show {
animation: show-item 2s ease-in forwards;
}
.hide {
animation: hide-item 2s ease-in forwards; /*forwards是讓動畫結束的時候保存最後一幀的樣式,不加它的話就不會隱藏*/
}
@keyframes hide-item {
0% {
opacity: 1;
color: red;
}
50% {
opacity: .5;
color: green;
}
100% {
opacity: 0;
color: blue;
}
}
@keyframes show-item {
0% {
opacity: 0;
color: red;
}
50% {
opacity: .5;
color: green;
}
100% {
opacity: 1;
color: blue;
}
}
此時運行後就有了動畫的效果。
13、使用react-transition-group實現動畫(一)
簡單的動畫我們可以自己寫,但是比較複雜的我們可以藉助這個第三方模塊 : react-transition-group
- 首先進行安裝 :
npm install react-transition-group --save
- App.js :
import React, { Component, Fragment } from 'react';
import { CSSTransition } from 'react-transition-group';
import './style.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
show: true
};
this.handleToggle = this.handleToggle.bind(this);
}
render() {
return (
<Fragment>
<CSSTransition
in={this.state.show} //根據show的值來判斷是出場動畫還是入場動畫
timeout={300} //動畫的時間
classNames='fade' //給類添加前綴名
>
<div>hello world</div>
</CSSTransition>
<button onClick={this.handleToggle}>toggle</button>
</Fragment>
)
}
handleToggle() {
this.setState({
show: this.state.show ? false : true
})
}
}
export default App;
- style.css :
/*執行入場動畫的時候,也就是show從false變成true的時候。在入場動畫執行的第一個時刻CSSTransition這個組件會往div上掛載一個樣式,名字就是fade-enter*/
.fade-enter {
opacity: 0;
}
/*入場動畫執行的第二個時刻到入場動畫完成之前的時刻,這段時間div上面一直會有fade-enter-active這個class*/
.fade-enter-active {
opacity: 1;
transition: opacity 1s ease-in;
}
/*當整個入場動畫執行完以後,這個class就會被增加到div上面*/
.fade-enter-done {
opacity: 1;
}
/*出場的第一個時刻,該class會被加上去*/
.fade-exit {
opacity: 1;
}
/*執行出場動畫的這段時間都會存在這個class*/
.fade-exit-active {
opacity: 0;
transition: opacity 1s ease-in;
}
/*整個出場動畫完成之後*/
.fade-exit-done {
opacity: 0;
}
此時我們就實現了和之前一樣的動畫效果。
雖然看起來我們實現一樣的效果,使用這個模塊來做更復雜,但是這個第三方的庫有着很多特別實用的地方:
- 給 CSSTransition 這個組件添加 unmountOnExit ,可以發現動畫結束後 div 元素就不存在,動畫開始它又出來了。
- 這個模塊還有很多鉤子函數(類似生命週期函數),我們可以使用這些鉤子函數實現特定的動畫。
render() {
return (
<Fragment>
<CSSTransition
in={this.state.show} //根據show的值來判斷是出場動畫還是入場動畫
timeout={300} //動畫的時間
classNames='fade' //給類添加前綴名
unmountOnExit
onEntered={(el) => {el.style.color='red'}} //el 指的就是內部的 div 元素,意思是當入場動畫執行結束之後,給元素添加紅色的字體顏色
>
<div>hello world</div>
</CSSTransition>
<button onClick={this.handleToggle}>toggle</button>
</Fragment>
)
添加配置:
appear={true} //第一次展示的時候就有動畫效果
一般這個組件都能滿足我們 react 開發中需要的動畫效果,到時候查文檔即可。
14、使用react-transition-group實現動畫(二)
1、實現多個元素的動畫效 App.js:
import React, { Component, Fragment } from 'react';
import { CSSTransition, TransitionGroup } from 'react-transition-group';
import './style.css';
class App extends Component {
constructor(props) {
super(props);
this.state = {
list: []
};
this.handleAddItem = this.handleAddItem.bind(this);
}
render() {
return (
<Fragment>
<TransitionGroup>
{
this.state.list.map((item, index) => {
return (
<CSSTransition
key={index}
timeout={300}
classNames='fade'
unmountOnExit
onEntered={(el) => {el.style.color='red'}}
appear={true}
>
<div>{item}</div>
</CSSTransition>
)
})
}
</TransitionGroup>
<button onClick={this.handleAddItem}>toggle</button>
</Fragment>
)
}
handleAddItem() {
this.setState((prevState) => {
return {
list: [...prevState.list, 'item']
}
})
}
}
export default App;
其實就是用 TransitionGroup 包起來, 內部每一項還是使用 CSSTransition 。