1. React簡介
- React 起源于 Facebook 的內部項目,因為該公司對市場上所有 JavaScript MVC 框架,都不滿意,就決定自己寫一套,用來架設 Instagram(照片交友) 的網站。做出來以后,發現這套東西很好用,就在2013年5月開源了。
- Angular1 2009 年 谷歌 MVC 不支持 組件化開發
- 由于 React 的設計思想極其獨特,屬于革命性創新,性能出眾,代碼邏輯卻非常簡單。所以,越來越多的人開始關注和使用,認為它可能是將來 Web 開發的主流工具。
- 清楚兩個概念:
- library(庫):小而巧的庫,只提供了特定的API;優點就是 船小好掉頭,可以很方便的從一個庫切換到另外的庫;但是代碼幾乎不會改變;
- Framework(框架):大而全的是框架;框架提供了一整套的解決方案;所以,如果在項目中間,想切換到另外的框架,是比較困難的;
2. 前端三大主流框架
三大框架一大抄
- Angular.js:出來較早的前端框架,學習曲線比較陡,NG1學起來比較麻煩,NG2 ~ NG5開始,進行了一系列的改革,也提供了組件化開發的概念;從NG2開始,也支持使用TS(TypeScript)進行編程;
- Vue.js:最火(關注的人比較多)的一門前端框架,它是中國人開發的,對我我們來說,文檔要友好一些;
- React.js:最流行(用的人比較多)的一門框架,因為它的設計很優秀;
3. React與vue的對比
組件化方面
- 什么是模塊化:是從代碼的角度來進行分析的;把一些可復用的代碼,抽離為單個的模塊;便于項目的維護和開發;
- 什么是組件化: 是從 UI 界面的角度 來進行分析的;把一些可服用的UI元素,抽離為單獨的組件;便于項目的維護和開發;
- 組件化的好處:隨著項目規模的增大,手里的組件越來越多;很方便就能把現有的組件,拼接為一個完整的頁面;
-
Vue是如何實現組件化的: 通過
.vue
文件,來創建對應的組件;- template 結構
- script 行為
- style 樣式
- React如何實現組件化:大家注意,React中有組件化的概念,但是,并沒有像vue這樣的組件模板文件;React中,一切都是以JS來表現的;因此要學習React,JS要合格;ES6 和 ES7 (async 和 await) 要會用;
開發團隊方面
- React是由FaceBook前端官方團隊進行維護和更新的;因此,React的維護開發團隊,技術實力比較雄厚;
- Vue:第一版,主要是有作者 尤雨溪 專門進行維護的,當 Vue更新到 2.x 版本后,也有了一個以 尤雨溪 為主導的開源小團隊,進行相關的開發和維護;
社區方面
- 在社區方面,React由于誕生的較早,所以社區比較強大,一些常見的問題、坑、最優解決方案,文檔、博客在社區中都是可以很方便就能找到的;
- Vue是近兩年才火起來的,所以,它的社區相對于React來說,要小一些,可能有的一些坑,沒人踩過;
移動APP開發體驗方面
- Vue,結合 Weex 這門技術,提供了 遷移到 移動端App開發的體驗(Weex,目前只是一個 小的玩具, 并沒有很成功的 大案例;)
- React,結合 ReactNative,也提供了無縫遷移到 移動App的開發體驗(RN用的最多,也是最火最流行的);
4. 為什么要學習React
- 和Angular1相比,React設計很優秀,一切基于JS并且實現了組件化開發的思想;
- 開發團隊實力強悍,不必擔心斷更的情況;
- 社區強大,很多問題都能找到對應的解決方案;
- 提供了無縫轉到 ReactNative 上的開發體驗,讓我們技術能力得到了拓展;增強了我們的核心競爭力;
- 很多企業中,前端項目的技術選型采用的是React.js;
5. React中幾個核心的概念
虛擬DOM(Virtual Document Object Model)
DOM的本質是什么:瀏覽器中的概念,用JS對象來表示 頁面上的元素,并提供了操作 DOM 對象的API;
什么是React中的虛擬DOM:是框架中的概念,是程序員 用JS對象來模擬 頁面上的 DOM 和 DOM嵌套;
為什么要實現虛擬DOM(虛擬DOM的目的):為了實現頁面中, DOM 元素的高效更新
DOM和虛擬DOM的區別:
DOM:瀏覽器中,提供的概念;用JS對象,表示頁面上的元素,并提供了操作元素的API;
-
虛擬DOM:是框架中的概念;而是開發框架的程序員,手動用JS對象來模擬DOM元素和嵌套關系;
+ 本質: 用JS對象,來模擬DOM元素和嵌套關系;
-
目的:就是為了實現頁面元素的高效更新;
虛擬DOM的概念.png
-
Diff算法
tree diff:新舊兩棵DOM樹,逐層對比的過程,就是 Tree Diff; 當整顆DOM逐層對比完畢,則所有需要被按需更新的元素,必然能夠找到;
-
component diff:在進行Tree Diff的時候,每一層中,組件級別的對比,叫做 Component Diff;
- 如果對比前后,組件的類型相同,則暫時認為此組件不需要被更新;
- 如果對比前后,組件類型不同,則需要移除舊組件,創建新組件,并追加到頁面上;
-
element diff:在進行組件對比的時候,如果兩個組件類型相同,則需要進行 元素級別的對比,這叫做 Element Diff;
[圖片上傳失敗...(image-19dc01-1595464286977)]
6. 創建項目
第一種方式
全局安裝create-react-app
npm install -g create-react-app
創建項目
create-react-app 項目名
第二種方式
npx
—— 它是 [npm 5.2+ 附帶的 package 運行工具],這種方式會先局部安裝腳手架,然后在安裝項目
npx create-react-app your-app
項目安裝這需要等待一段時間,這個過程實際上會安裝三個東西
- react: react的頂級庫,專門用于創建組件和虛擬DOM的,同時組件的生命周期都在這個包中
- react-dom:專門進行DOM操作的,最主要的應用場景,就是
ReactDOM.render()
- react-scripts: 包含運行和打包react應用程序的所有腳本及配置
7. 創建虛擬dom并渲染
1.創建虛擬DOM元素:
// 這是 創建虛擬DOM元素的 API <h1 title="啊,五環" id="myh1">你比四環多一環</h1>
// 第一個參數: 字符串類型的參數,表示要創建的標簽的名稱
// 第二個參數:對象類型的參數, 表示 創建的元素的屬性節點
// 第三個參數: 子節點
const myh1 = React.createElement('h1', { title: '啊,五環', id: 'myh1' }, '你比四環多一環')
2.渲染:
// 3. 渲染虛擬DOM元素
// 參數1: 表示要渲染的虛擬DOM對象
// 參數2: 指定容器,注意:這里不能直接放 容器元素的Id字符串,需要放一個容器的DOM對象
ReactDOM.render(myh1, document.getElementById('app'))
注:像這樣創建虛擬dom太麻煩了,
React.createElement('h1', { title: '啊,五環', id: 'myh1' }, '你比四環多一環')
所以有了jsx 語法,寫jsx語法,webpack的babel-loader會轉化為這樣
React.createElement('h1', { title: '啊,五環', id: 'myh1' }, '你比四環多一環')
8. JSX語法
什么是JSX語法:
就是符合 xml 規范的 JS 語法;(語法格式相對來說,要比HTML嚴謹很多)
REACT獨有的語法 JAVASCRIPT+XML(HTML)
和我們之前自己拼接的HTML字符串類似,都是把HTML結構代碼和JS代碼或者數據混合在一起了,
但是它不是字符串
下面這些安裝,如果用腳手架創建的項目,腳手架已經做了,不用安裝了
- 如何啟用 jsx 語法?
-
安裝
babel
插件- 運行
cnpm i babel-core babel-loader babel-plugin-transform-runtime -D
- 運行
cnpm i babel-preset-env babel-preset-stage-0 -D
- 運行
-
安裝能夠識別轉換jsx語法的包
babel-preset-react
- 運行
cnpm i babel-preset-react -D
- 運行
-
添加
.babelrc
配置文件{ "presets": ["env", "stage-0", "react"], "plugins": ["transform-runtime"] }
-
添加babel-loader配置項:
module: { //要打包的第三方模塊 rules: [ { test: /\.js|jsx$/, use: 'babel-loader', exclude: /node_modules/ } ] }
jsx 語法的本質:并不是直接把 jsx 渲染到頁面上,而是 內部先轉換成了 createElement 形式,再渲染的;
-
在 jsx 中混合寫入 js 表達式:在 jsx 語法中,要把 JS代碼寫到
{ }
中,但是要求JS代碼指執行完成有返回結果(JS表達式)- 渲染數字
- 渲染字符串
- 渲染布爾值
- 為屬性綁定值
- 渲染jsx元素
- 渲染jsx元素數組
在 jsx 中 寫注釋:推薦使用
{ /* 這是注釋 */ }
為 jsx 中的元素添加class類名:需要使用
className
來替代class
;htmlFor
替換label的for
屬性在JSX創建DOM的時候,所有的節點,必須有唯一的根元素進行包裹;
在 jsx 語法中,標簽必須 成對出現,如果是單標簽,則必須自閉和!
當 編譯引擎,在編譯JSX代碼的時候,如果遇到了
<
那么就把它當作 HTML代碼去編譯,如果遇到了
{}
就把 花括號內部的代碼當作 普通JS代碼去編譯;
9. React中創建組件
不管是VUE還是REACT框架,設計之初都是期望我們按照“組件/模塊管理”的方式來構建程序的,也就是把一個程序劃分為一個個的組件來單獨處理
優勢
1.有助于多人協作開發
2.我們開發的組件可以被復用REACT中創建組件有兩種方式:
函數聲明式組件
基于繼承COMPONENT類來創建組件SRC -> COMPONENT :這個文件夾中存放的就是開發的組件
返回的jsx,必須是唯一的節點包裹,如果不想讓這個節點展示,可以用這個包裹 <> <h1>hhh</h1>
? <h1>hhh</h1> </>
第1種 - 函數聲明式組件(無狀態組件)
函數聲明式組件,如果要接收外界傳遞的數據,需要在 構造函數的參數列表中使用
props
來接收;必須要向外return一個合法的JSX;
-
創建組件:
//Dialog.js import React from 'react'; //=>每一個組件中都要導入REACT,因為需要基于它的CREATE-ELEMENT把JSX進行解析渲染呢 /* * 函數式聲明組件 * 1.函數返回結果是一個新的JSX(也就是當前組件的JSX結構) * 2.PROPS變量存儲的值是一個對象,包含了調取組件時候傳遞的屬性值(不傳遞是一個空對象) * 3.children 代表雙閉合組件中的子元素,相當于vue中插槽的概念 * */ export default function Dialog(props) {//props形參,接收外界傳遞過來的數據,只讀的;不能被重新賦值; let {con, lx = 0, style = {},children} = props, //=>children(代表雙閉合組件中的子元素) return <section style={style}> <h2>{title}</h2> <div>{con}</div> {children} </section>; };
-
使用組件,并為組件傳遞數據:
import React from 'react'; import ReactDOM from 'react-dom';
import Dialog from './component/Dialog'; //引入組件,組件的名稱首字母必須是大寫
ReactDOM.render(<div>
{/注釋:JSX中調取組件,只需要把組件當做一個標簽調取使用即可(單閉合和雙閉合都可以)/}
<Dialog con='哈哈哈' style={{color: 'red'}}/>
{/*屬性值不是字符串,我們需要使用大括號包起來*/}
<Dialog con='嘿嘿嘿' lx={1} style={{color: 'red'}}>
<span>1</span> //這個雙閉合標簽里面的內容就是用children接受
<span>2</span>
</Dialog>
</div>, root);
### 第2種 - 創建類式(有狀態組件)
>必須繼承 React.Component 和有一個render函數并且返回jsx
* 創建組件
```js
import React from 'react'
export default class Dialog extends React.Component {
render() {
//“this.props組件的屬性是只讀的”
return <section>
<h3>{this.props.lx}</h3>
<div>{this.props.con}</div>
</section>;
}
}
-
使用組件
import React from 'react'; import ReactDOM from 'react-dom'; import Dialog from './01-component/Dialog' //引入組件 ReactDOM.render( <Dialog con='哈哈哈' lx={10}/>, //使用組件 document.getElementById('root') );
兩種創建組件方式的對比
- 用函數創建出來的組件:叫做“無狀態組件”
- 用class關鍵字創建出來的組件:叫做“有狀態組件”
- 用函數**創建出來的組件:叫做“無狀態組件”【無狀態組件今后用的不多】
- 用class關鍵字創建出來的組件:叫做“有狀態組件”【今后用的最多】
- 什么情況下使用有狀態組件?什么情況下使用無狀態組件?
- 如果一個組件需要有自己的私有數據,則推薦使用:class創建的有狀態組件;
- 如果一個組件不需要有私有的數據,則推薦使用:無狀態組件;
- React官方說:無狀態組件,由于沒有自己的state和生命周期函數,所以運行效率會比 有狀態組件稍微高一些;
有狀態組件和無狀態組件之間的本質區別就是:有無state屬性、和 有無生命周期函數;
10.組件的數據掛載方式
屬性(props)
props
是正常是外部傳入的,屬性不能被組件自己更改,組件內部也可以設置默認值,字符類型,屬性是描述性質、特點的,組件自己不能隨意更改。
在使用一個組件的時候,可以把參數放在標簽的屬性當中,所有的屬性都會作為組件
props
對象的鍵值。通過箭頭函數創建的組件,需要通過函數的參數來接收props
給屬性設置規則,需要安裝第三方插件
yarn add prop-types
給屬性設置默認值和規則
import React from 'react'
import PropTypes from 'prop-types';
export default class Dialog extends React.Component {
//THIS.PROPS是只讀的,我們無法在方法中修改它的值,但是可以給其設置默認值
static defaultProps = {
lx: '系統提示'
};
//PROP-TYPES是FACEBOOK公司開發的一個插件
基于這個插件我們可以給組件傳遞的屬性設置規則
static propTypes = {
//=>不僅傳遞的內容是字符串,并且還必須傳遞
con: PropTypes.string.isRequired,
//=>傳遞的內容需要是數字
lx: PropTypes.number,
//=>傳遞的內容需要是數組
city:PropTypes.array,
//=>數組中的每一項需要是字符串
list:PropTypes.arrayOf(PropTypes.string),
//=>自定義函數驗證
address:function(props,propname){
if(props[propname] !=='aa'){
return new Error('內容非法')
}
//console.log(arguments);
}
};
render() {
//“this.props組件的屬性是只讀的”
return <section>
<h3>{this.props.lx}</h3>
<div>{this.props.con}</div>
</section>;
}
}
狀態state
react中的state相當于vue中的data屬性
state顧名思義就是狀態,它只是用來控制這個組件本身自己的狀態,我們可以用state來完成對行為的控制、數據的更新、界面的渲染,由于組件不能修改傳入的props,所以需要記錄自身的數據變化。
更改狀態,<span style="color:red">只能用 setState()方法</span>,當我們調用這個函數的時候,<span style="color:red">React.js 會更新組件的狀態 state ,并且重新調用 render 方法</span>,然后再把 render 方法所渲染的最新的內容顯示到頁面上
setState 本身在生命周期函數或者在合成事件中執行是異步的
在保證生命周期函數執行的順序不紊亂
保證其實現渲染隊列的機制(可以合并setState統一渲染)
在原生的事件綁定中和其他異步操作中的set-state是同步的,沒有渲染隊列效果了
import React, { Component } from 'react'
export default class movie extends Component {
constructor(){
super()
//state定義的第一種方式
//設置狀態
this.state = {
title:"中國機長"
}
}
//state定義的第二種方式
// state ={
// title:"中國機長",
// count:1
// }
componentDidMount(){
//更改狀態,因為是異步的,想立即獲取更改后的內容,可以用第2個參數:回調函數
//更改state第一種方式
this.setState({
title:"戰狼"
},() => {
//相當于vue $nextTick
console.log(this.state.title);
}
)
//更改state的第二種方式
//this.state.title="戰狼"
//this.setState({})
}
render() {
return (
<div>
{this.state.title}
</div>
)
}
}
組件中的 props
和 state/data
之間的區別
- props 中的數據都是外界傳遞過來的;
- state/data 中的數據,都是組件私有的;(通過 Ajax 獲取回來的數據,一般都是私有數據);
- props 中的數據都是只讀的;不能重新賦值;
- state/data 中的數據,都是可讀可寫的;
狀態提升
如果有多個組件共享一個數據,把這個數據放到共同的父級組件中來管理
受控組件與非受控組件
受控組件
其實就相當于vue中的v-model指令,在react中是自己手動實現,用onChange事件
<span style="color:red">雙向數據綁定就是受控組件</span>
受控組件就是可以被 react 狀態控制的組件
在 react 中,Input textarea 等組件默認是非受控組件(輸入框內部的值是用戶控制,和React無關)。但是也可以轉化成受控組件,<span style="color:red">就是通過 onChange 事件獲取當前輸入內容,將當前輸入內容作為 value 傳入,此時就成為受控組件</span>。
好處:可以通過 onChange 事件控制用戶輸入,過濾不合理輸入。image.png
import React, { Component} from 'react'; export default class Form extends Component { constructor() { super(); this.state = { name: "", desc: "", } } handleChange(event) { this.setState({ [event.target.name]: event.target.value //把表單中的值賦值給狀態 }) } render() { return ( <div>//表單中的value,綁定了state上的狀態值 <input type="text" value={this.state.name} name="name" onChange={this.handleChange.bind(this)} /> <textarea value={this.state.desc} name="desc" onChange={this.handleChange.bind(this)}> </textarea> </div> ); } }
非受控組件
不受狀態控制的就是非受控組件
基于REF操作DOM實現視圖更新的,叫做“非受控組件”
使用場景,必須手動操作DOM元素,setState 實現不了的,例如文件上傳 <input type=file>
import React,{Component} from 'react'; import ReactDOM from 'react-dom'; export default class Sum extends Component{ handleChange=(event)=>{ let a = parseInt(this.refs.a.value||0); let b = parseInt(this.refs.b.value||0); this.refs.result.value = a+b; } render(){ return ( //經過React封裝可以onChange可以寫在div上 <div onChange={this.handleChange}> <input type="text" ref="a" /> + <input type="text" ref="b" /> = <input type="text" ref="result" /> </div> //input是非受控組件,因為不受狀態控制 ) } }
11.組件中DOM樣式
-
行內
import React from 'react' export default class Movie extends React.Component { render() { return <div> {/* 行內樣式 */} <h1 style={{ fontSize: '20px', color: 'red' }}>正在熱映</h1> </div> } }
-
外部引入
<span style="color:red">這種引入方式是全局生效</span>
import React from 'react' import './Movie.css' //從外部引入 export default class Movie extends React.Component { render() { return <div className="wrap"> <h1>正在熱映</h1> </div> } }
-
模塊化
<span style="color:red">這種引入方式是局部生效</span>,目的解決全局沖突
-
Vue 組件中的樣式表,有沒有 沖突的問題???
Vue 組件中的樣式表,也有沖突的問題;但是,可以使用 <style scoped></style>
React 中,有沒有類似于 scoped 這樣的指令呢?
? 沒有;因為 在 React 中,根本就沒有指令的概念;
? 模塊塊就是為了解決沖突的問題,局部生效
文件名必須是 *.module.css結尾,同樣的,如是要是
.sass
或.scss
的話,文件名格式應該是[name].module.sass
或[name].module.scss
其實模塊化,就是webpack給css-loader配置了參數modules
module: { rules: [ // 其中,有個固定的參數,叫做 modules , 表示為 普通的 CSS 樣式表,啟用模塊化 { test: /\.scss$/, use: ['style-loader', 'css-loader?modules', 'sass-loader'] } ] }
import React from 'react' import common from "./common.module.css" //引入css文件 export default class Movie extends React.Component { render() { return <div> <h1 className={common.error}> //使用css gggg </h1> </div> } }
//common.module.css 文件 .error{ color:red; font-size: 30px; }
-
-
不同的條件添加不同的樣式
- 安裝
yarn add classnames
特點就是根據動態數據控制樣式
局部,不會全局污染
代碼:
import React from 'react' import classNames from 'classnames/bind' //引入第三方插件 import styles from './styles.css' //引入樣式 let cx = classNames.bind(styles) //綁定在一塊 export default class profile extends React.Component{ render(){ let names = cx({ inProcess:true, //根據數據來決定使用哪一個樣式 error:false }) return <div> <h1 className={names}>gp18</h1> </div> } }
//styles.css文件 .inProcess { color: orange; font-size: 30px; } .error { color: red; font-size: 30px; }
-
css-in-js
styled-components`是針對React寫的一套css-in-js框架,簡單來講就是在js中寫css
解決全局污染
安裝
yarn add styled-components
代碼:
import React from 'react' import {Wrap} from './search-style' //引入樣式 export default class Search extends React.Component{ render(){ return <Wrap color="red"> //使用 yyyy <h1>hhh</h1> </Wrap> } }
//search-style.js 文件 import React from 'react' import styled from 'styled-components' //引入第三方插件 const Wrap = styled.div` width:500px; height:500px; background:${(props)=>props.color}; //動態賦值 font-size:30px; h1 { font-size:50px; } ` export { Wrap }
12.事件處理
采用on+事件名的方式來綁定一個事件,注意,這里和原生的事件是有區別的,原生的事件全是小寫
onclick
, React里的事件是駝峰onClick
,React的事件并不是原生事件,而是合成事件。Event對象
和普通瀏覽器一樣,事件handler會被自動傳入一個
event
對象,這個對象和普通的瀏覽器event
對象所包含的方法和屬性都基本一致。不同的是 React中的event
對象并不是瀏覽器提供的,而是它自己內部所構建的。它同樣具有event.stopPropagation
、event.preventDefault
這種常用的方法
-
事件的幾種寫法,目的能訪問到this
- 第一種寫法 <span style="color:red"> (不能傳遞參數)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = { name: 'gp18' } } handleClick=(event) => { //箭頭函數 console.log(this.state.name); } render() { return ( <div> /*這種綁定事件的方法,要想this能訪問到, handleClick函數必須用箭頭函數 */ <button onClick={this.handleClick}>add</button> </div> ) } }
- 第二種寫法 <span style="color:red"> (不能傳遞參數)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = { name: 'gp18' } //@key this.myhandleclick = this.handleClick.bind(this) } //@key 最后追加一個參數,即可接受event handleClick(val, event) { console.log(val, event, this.state.name); } render() { return ( <div> <button onClick={this.myhandleclick} //@key >添加</button> </div> ) } }
- 第三種寫法 <span style="color:red"> (可以傳遞參數)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = {name: 'gp18'} } //觸發函數 handleClick(val,event){ console.log(this.state.name,val,event); } render() { return ( <div> //因為handleClick函數不是箭頭函數,需要改變this指向 <button onClick={this.handleClick.bind(this,'abc')} >添加</button> </div> ) } }
- 第四種寫法 <span style="color:red"> (可以傳遞參數)</span>
import React, { Component } from 'react' export default class event1 extends Component { constructor() { super() this.state = {name: 'gp18'} } handleClick(val,event){ console.log(this.state.name,val,event); } render() { return ( <div> <button onClick={ (event) =>{return this.handleClick('abc',event)} }>添加</button> </div> ) } }
13. 組件生命周期
- 生命周期的概念
每個組件的實例,從創建、到運行、直到銷毀,在這個過程 中,會出發一些列 事件,這些事件就叫做組件的生命周期函數;
生命周期分為三部分:
組件創建階段
組件創建階段的生命周期函數,有一個顯著的特點:創建階段的生命周期函數,在組件的一輩子中,只執行一次;
- componentWillMount
? 組件將要被掛載,此時還沒有開始渲染虛擬DOM
- render:
? 第一次開始渲染真正的虛擬DOM,當render執行完,
? 內存 中就有了完整的虛擬DOM了
- componentDidMount
? 組件完成了掛載,此時,組件已經顯示到了頁面上,
? 當這個方法執行完,組件就進入都了 運行中 的狀態
? 如果要初始化第三方的dom庫,也在這里進行初始化。只有到這里才能獲取到真實的dom.
? 通常在這里進行ajax請求
- 組件運行階段
也有一個顯著的特點,根據組件的state和props的改變,有選擇性的觸發0次或多次;
- componentWillReceiveProps:
? 組件將要接收新屬性,此時,只要這個方法被觸發,
? 就證明父組件為當前子組件傳遞了新的屬性值;
props改變的時候
- shouldComponentUpdate:
? 組件是否需要被更新,此時,組件尚未被更新,
? 但是,state 和 props 肯定是最新的
? 在渲染新的
props
或state
前,shouldComponentUpdate
會被調用。默認為true
。? 這個方法不會在初始化時被調用,也不會在
forceUpdate()
時被調用。? 返回
false
不會阻止子組件在state
更改時重新渲染。? 如果
shouldComponentUpdate()
返回false
,componentWillUpdate
,render
和componentDidUpdate
不會被調用。
- componentWillUpdate:
? 組件將要被更新,此時,尚未開始更新,
? 內存中的虛擬DOM 樹還是舊的
- render:
? 此時,又要重新根據最新的 state 和 props
重新渲染一棵內存中的 虛擬DOM樹,當 render 調用完畢,內存中的舊DOM樹,
? 已經被新DOM樹替換了!此時頁面還是舊的
- componentDidUpdate
? 此時,頁面又被重新渲染了,state 和 虛擬DOM 和頁面已經 完全保持同步
- 組件銷毀階段
也有一個顯著的特點,一輩子只執行一次;
- componentWillUnmount:
? 組件將要被卸載,此時組件還可以正常使用;
? 在組件被卸載并銷毀之前立即被調用。在此方法中執行任何必要的清理,
? 例如使定時器無效,取消網絡請求或清理在
componentDidMount
中創建的任何監聽
-
生命周期圖
image.png
新版本v16.4 的生命周期函數
- v17版本
componentWillMount,componentWillReceiveProps,componentWillUpdate這三個函數將要作廢
v16.4新增的生命周期函數
getDerivedStateFromProps
//可以拿到父組件傳遞過來的屬性,同時可以拿到當前組件的state //可以把傳遞過來的屬性合并到當前組件state上 static getDerivedStateFromProps(props, state) { console.log('getDerivedStateFromProps:', props, state) return { ...props, ...state } }
getSnapshotBeforeUpdate()
在react
render()
后的輸出被渲染到DOM之前被調用。它使您的組件能夠在它們被潛在更改之前捕獲當前值(如滾動位置)。這個生命周期返回的任何值都將作為參數傳componentDidUpdate()只要寫這個生命周期函數就必須寫componentDidUpdate
getSnapshotBeforeUpdate(prevProps, prevState) { console.log("getSnapshotBeforeUpdate:", prevProps, prevState) return { username:'hanye', age:20 }; } componentDidUpdate(prevProps, prevState,sanpshot){ console.log("componentDidUpdate:",prevProps, prevState,sanpshot) }
PureComponent
PureComponnet
里如果接收到的新屬性或者是更改后的狀態和原屬性、原狀態相同的話,就不會去重新render了
在里面也可以使用shouldComponentUpdate
,而且。是否重新渲染以shouldComponentUpdate
的返回值為最終的決定因素。此方法僅作為性能優化的方式而存在。不要企圖依靠此方法來“阻止”渲染,因為這可能會產生 bug。你應該**考慮使用內置的 [PureComponent
](當 props 或 state 發生變化時,shouldComponentUpdate()
會在渲染執行之前被調用。返回值默認為 true。首次渲染或使用forceUpdate()
時不會調用該方法。)import React, { Component,PureComponent } from 'react'; export default class Parent extends PureComponent { //@Key constructor() { super(); this.state = { count: 1,} } render() { return ( <div id="app"> <button onClick={this.handleClick}> 強制更新 </button> </div> ); } handleClick = () => { this.state.count = 100; this.forceUpdate(); //@key } }
14 HOC高階組件
- 定義
具體而言,高階組件是參數為組件,返回值為新組件的函數
const EnhancedComponent = higherOrderComponent(WrappedComponent);
- 作用
在多個不同的組件中需要用到相同的功能,這個解決方法,通常是高階組件。
- 例子
//定義一個高階組件 Hoc.js import React, { Component } from 'react' const Hoc = (WrapperCommonent)=>{ return class Copyright extends Component { render() { return ( <div> <WrapperCommonent></WrapperCommonent> <div>版權所有</div> </div> ) } } } export default Hoc //使用高階組件 import React, { Component } from 'react' import Hoc from './Hoc' class Base extends Component { render() { return ( <div> react.js 是一個構建用戶節目的庫 </div> ) } } export default Hoc(base) //@Key
15.組件通信
父組件與子組件通信
父組件將自己的狀態傳遞給子組件,子組件當做屬性來接收,當父組件更改自己狀態的時候,子組件接收到的屬性就會發生改變
//父組件 import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { constructor(){ super() this.state = {count:1} } render() { return ( <Child count={this.state.count}></Child> //狀態傳遞給子組件 ) } } //子組件 import React, {Component} from 'react' export default class Child extends Component { render() { return <div> {this.props.count} //子組件當做屬性來接收 </div> } }
子組件與父組件通信
父組件將自己的某個方法傳遞給子組件,在方法里可以做任意操作,比如可以更改狀態,子組件通過
this.props
接收到父組件的方法后調用。//父組件 import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { constructor(){ super() this.state = {count:1} } handclick=()=>{ this.setState({ count:10 }) } render() { return ( <div> {this.state.count} <Child fn={this.handclick}></Child> //將方法傳遞給子組件 </div> ) } } //子組件 import React, {Component} from 'react' export default class Child extends Component { componentDidMount(){ this.props.fn() } render() { return <div> child </div> } }
跨組件通信 context
在react沒有類似vue中的事件總線來解決這個問題,我們只能借助它們共同的父級組件來實現,將非父子關系裝換成多維度的父子關系。react提供了
context
api來實現跨組件通信, React 16.3之后的context
api較之前的好用。在父組件上提供數據,其他后代組件,都能訪問到該數據
舉個計數器的例子,這個例子是context在實際項目這樣寫,具體基礎的看文檔
//Mycontext.js import React, { Component,createContext} from 'react' const {Provider,Consumer:MyConsumer} = createContext() class MyProvider extends Component { constructor(){ super() this.state = {count:1} } incr=()=>{ this.setState({ count:this.state.count+1 }) } render() { return ( //需要共享的數據寫在value里 <Provider value={{...this.state,'incr':this.incr,'decr':this.decr}}> {this.props.children} </Provider> ) } } export {MyProvider,MyConsumer} //父組件 Parent.js import React, { Component } from 'react' import Child from './Child' import {MyProvider} from './Mycontext' export default class Parent extends Component { render() { return ( <MyProvider> <Child></Child> </MyProvider> ) } } //后代組件 Child.js import React, { Component } from 'react' import {MyConsumer} from './Mycontext' export default class Button extends Component { render() { return (//后代訪問共享數據 <MyConsumer> { ({incr,count})=>{ return <button onClick={incr}>add按鈕:{count}</button> } } </MyConsumer> ) } }
redux
什么是redux?
進行狀態統一管理的類庫(適應于任何技術體系的項目)
1.只要兩個或多個組件之間想要實現信息的共享,都可以基于redux解決,把共享的信息存儲到redux容器進行管理
2.還可以做臨時存儲,頁面加載的時候,把從服務器獲取的數據信息存儲到redux中
為什么要用redux
因為對于react來說,同級組件之間的通信尤為麻煩,或者是非常麻煩了,所以我們把所有需要多個組件使用的state拿出來,整合到頂部容器,進行分發
需要使用redux的項目
- 用戶的使用方式復雜
- 不同身份的用戶有不同的使用方式(比如普通用戶和管理員)
- 多個用戶之間可以協作
- 與服務器大量交互,或者使用了WebSocket
- View要從多個來源獲取數據
從組件層面考慮,什么樣子的需要Redux:
- 某個組件的狀態,需要共享
- 某個狀態需要在任何地方都可以拿到
- 一個組件需要改變全局狀態
- 一個組件需要改變另一個組件的狀態
Redux的設計思想
- Web 應用是一個狀態機,視圖與狀態是一一對應的。
- 所有的狀態,保存在一個對象里面(唯一數據源)
Redux的使用的三大原則
- Single Source of Truth(唯一的數據源)
- State is read-only(狀態是只讀的)
- Changes are made with pure function(數據的改變必須通過純函數完成)
Redux的流程
image.png
Store的角色是整個應用的數據存儲中心,集中大部分頁面需要的狀態數據;
ActionCreators ,view 層與data層的介質;
Reduce ,接收action并更新Store。
所以流程是 用戶通過界面組件 觸發ActionCreator,攜帶Store中的舊State與Action 流向Reducer,Reducer返回新的state,并更新界面。自己實現的redux
<!DOCTYPE html> <html lang="en"> <body> <button onclick="store.dispatch({type:'DECR',num:1})">-</button> <h1 id="count"></h1> <button onclick="store.dispatch({type:'INCR',num:1})">+</button> <script> //集中管理state,和dispatch,action const createStore = (reducer) => { let state; const getState = () => state //定義一個監聽器隊列,管理所有訂閱的方法 const listeners = [] const subscribe = (listener) => listeners.push(listener) const dispatch = (action) => { //1.執行REDUCER,修改容器中的狀態信息 state = changeState(state, action) //2.容器中狀態信息經過REDUCER修改后,通知事件池中的方法依次執行 listeners.forEach((item) => { item() }) } //=>創建容器的時候執行一次DISPATCH, //=>目的是把REDUCER中的默認狀態信息賦值給REDUX容器中的狀態 dispatch({ type: '$$INIT_DEFAULT_STATE' }); return { getState, subscribe, dispatch } } //其實這個方法就是寫自己的邏輯的地方 const changeState = (state = { count: 0 }, action) => { switch (action.type) { case "INCR": return { count: state.count + action.num } case "DECR": return { count: state.count - action.num } default: return state } } const store = createStore(changeState) store.subscribe(render) function render() { document.getElementById("count").innerHTML = store.getState().count; } render() </script> </body> </html>
react-redux
安裝
yarn add redux react-redux yarn add redux-thunk //異步請求數據要用到這個插件
redux
適應于所有項目,store = createStore(reducer),然后在需要的地方通過store.getState()去獲取數據,通過store.dispatch去更新數據,通過store.subscribe去訂閱數據變化然后進行setState...如果很多地方都這樣做一遍,實在是不堪其重
REACT-REDUX
描述
? react-redux只是適應react項目
? 是把REDUX進一步封裝,適配REACT項目,讓REDUX操作更簡潔
? STORE文件夾中的內容和REDUX一模一樣
? 在組件調取使用的時候可以優化一些步驟
相對于傳統的REDUX,我們做的步驟優化
? 導出的不在是我們創建的組件,而是基于CONNECT構造后的高階組件
? export default connect([mapStateToProps], [mapDispatchToProps])([自己創建的組件]);
? REACT-REDUX幫我們做了一件非常重要的事情:
? 以前我們需要自己基于SUBSCRIBE向事件池追 加方法,以達到容器狀態信息改變,
? 執行我們追加的方法,重新渲染組件的目的,但是現在不用了,
? REACT-REDUX幫我們做了這件事:“所有用到REDUX容器狀態信息的組件,都會向事件池 中追加一個方法,當狀態信息改變,通知方法執行,
? 把最新的狀態信息作為屬性傳遞給組件,組件的屬性值改變了,組件也會重新渲染”
react-redux 其實就提供了這四個東西
Provider 根組件
當前整個項目都在Provider組件下
作用就是把創建的STORE可以供內部任何后代組件使用(基于上下文完成的)
=>Provider組件中只允許出現一個子元素
=>把創建的STORE基于屬性傳遞給Provider(這樣后代組件中都可以使用這個STORE了)
redux的時候,每個組件想使用store,屬性都得傳store
這個函數的底層實現原理
? 其實就是根據上下文實現的
? PROVIDER:當前項目的“根”組件
? 接收通過屬性傳遞進來的STORE,把STORE掛載到上下文中,
? 這樣當前項目中任何一個組件中,想要使用REDUX中的STORE,
? 直接通過上下文獲取即可
class Provider extends React.Component { //=>設置上下文信息類型 static childContextTypes = { store: PropTypes.object }; //=>設置上下文信息值 getChildContext() { return { store: this.props.store }; } constructor(props, context) { super(props, context); } render() { return this.props.children; } }
- connect 高階組件
CONNECT:高階組件(基于高階函數:柯理化函數)創建的組件就是高階組件 @PARAMS mapStateToProps:回調函數,把REDUX中的部分狀態信息掛載到指定組件的屬性上
function mapStateToProps(state){ //=>state:REDUX容器中的狀態信息 return {}; //=>RETURN對象中有啥,就把啥掛載到屬性上 } ``` mapDispatchToProps:回調函數,把一些需要派發的任務方法也掛載到組件的屬性上 ``` function mapDispatchToProps(dispatch){ //=>dispatch:store中的dispatch return { init(){ dispatch({...}); } }; //=>RETURN啥就把啥掛載到屬性上(返回的方法中有執行dispatch派發任務的操作) } ``` @RETURN 返回一個新的函數 CONNECT-HOT
====
CONNECT-HOT
@PARAMS
傳遞進來的是要操作的組件,我們需要把指定的屬性和方法都掛載到當前組件的屬性上@RETURN
返回一個新的組件Proxy(代理組件),在代理組件中,我們要獲取Provider在上下文中存儲的store,緊接著獲取store中的state和dispatch,把mapStateToProps、mapDispatchToProps回調函數執行,接收返回的結果,在把這些結果掛載到Component這個要操作組件的屬性上function connect(mapStateToProps, mapDispatchToProps) {
return function connectHOT(Component) {
return class Proxy extends React.Component {
//=>獲取上下文中的STORE
static contextTypes = {
store: PropTypes.object
};constructor(props, context) { super(props, context); this.state = this.queryMountProps(); } //=>基于REDUX中的SUBSCRIBE向事件池中追加一個方法,當容器中狀態改變,我們需要重新獲取最新的狀態信息,并且重新把COMPONENT渲染,把最新的狀態信息通過屬性傳遞給COMPONENT componentDidMount() { this.context.store.subscribe(() => { this.setState(this.queryMountProps()); }); } //=>渲染COMPONENT組件,并且把獲取的信息(狀態、方法)掛載到組件屬性上(單獨調取POXY組件的是時候傳遞的屬性也給COMPONENT) render() { return <Component {...this.state} {...this.props}/> } //=>從REDUX中獲取最新的信息,基于回調函數篩選,返回的是需要掛載到組件屬性上的信息 queryMountProps = () => { let {store} = this.context, state = store.getState(); let propsState = typeof mapStateToProps === 'function' ? mapStateToProps(state) : {}; let propsDispatch = typeof mapDispatchToProps === 'function' ? mapDispatchToProps(store.dispatch) : {}; return { ...propsState, ...propsDispatch }; }; } }
}
- mapStateToProps函數
let mapStateToProps = state => { //=>state:就是REDUX容器中的狀態信息 //=>我們返回的是啥,就把它掛載到當前組件的屬性上 //=>(REDUX存儲很多信息,我們想用啥就返回啥即 可) return { ...state.vote }; };
- mapDispatchToProps
//=>把REDUX中的DISPATCH派發行為遍歷,也賦值給組件的屬性(ActionCreator) let mapDispatchToProps = dispatch => { //=>dispatch:STORE中存儲的DISPATCH方法 //=>返回的是啥,就相當于把啥掛載到組件的屬性上 //(一般我們掛載一些方法,這些方法中完成了DISPATCH派發任務操作) return { init(initData) { //action.vote.init(initData)返回的就是類似{type:'aa',...} dispatch(action.vote.init(initData)); } }; }; export default connect(state => ({...state.vote}), action.vote)(VoteBase);//=>REACT-REDUX幫我們做了一件事情,把ACTION-CREATOR中編寫的方法(返回ACTION對象的方法),自動構建成DISPATCH派發任務的方法,也就是mapDispatchToProps這種格式
具體使用
目錄
store
? reducer 存放每一個模塊的reducer
? vote.js
? personal.js
? ...
? index.js 把每一個模塊的reducer最后合并成為一個reducer
? action 存放每一個模塊需要進行的派發任務(ActionCreator)
? vote.js
? personal.js
? ...
? index.js 所有模塊的ACTION進行合并
? action-types.js 所有派發任務的行為標識都在這里進行宏觀管理
? index.js 創建STORE
代碼
reducer目錄的代碼
image.pngaction目錄的代碼
aa.jpegaction-types.js 文件
image.png
index.js 文件
image.png
使用
在入口index.js提供數據store
image.png
- 在子組件消費數據
image.png
16 路由
安裝
react-router-dom
第一個例子:基本路由
基本使用
路由跳轉
匹配不到返回404
兩種方式的傳參及接受參數
使用component屬性渲染組件
import React, { Component } from 'react' import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom' function Home(props) { //獲取參數 let result = new URLSearchParams(useLocation().search) console.log(result.get("city")); return <h2>Home</h2> } function About(props) { //獲取參數 console.log(props.match.params.id); return <h2>About</h2>; } export default class index extends Component { render() { return ( <Router> //必須用Router包裹 <Link to="/">首頁</Link> <Link to="/about/66">關于</Link> <Link to="/my">我的</Link> <hr/> <Switch> //Switch:排他性,只能匹配到一個路由 <Route exact path="/index/" component={Home}></Route> <Route path="/about/:id" component={About}></Route> <Route path="/my">我的</Route> <Redirect exact from="/" to="/index?city=北京"></Redirect> <Route>404</Route> </Switch> </Router> ) } }
第二例子 嵌套路由
//一級路由 import React, { Component } from 'react' import {BrowserRouter as Router,Route,Link,useLocation,Redirect,Switch} from 'react-router-dom' import Order from './Order' export default class index extends Component { render() { return ( <Router> <Link to="/order">訂單</Link> <Route path="/order" component={Order}></Route> </Router> ) } } //二級路由 Order.js import React, { Component } from 'react' import {Link,Route,Switch} from 'react-router-dom' export default class Movie extends Component { render() { return ( <div> <Link to="/order/pay">已支付</Link> <Link to="/order/unpay">未支付</Link> <hr/> <Switch> <Route exact path="/order/pay">pay</Route> <Route exact path="/order/unpay">unpay</Route> </Switch> </div> ) } }
第三個例子 render
用render函數渲染組件
可以根據邏輯,來判斷渲染哪一個組件
import React, { Component } from 'react' import {BrowserRouter as Router,Route,Switch,Link} from 'react-router-dom' function Login(props) { return <h2>Login</h2>; } function Pos(props) { return <h2>Pos</h2>; } export default class index extends Component { constructor(){ super() this.state = {isLogin:false} } render() { return ( <Router> <Link to="/index">首頁</Link> <Link to="pos">職位管理</Link> <Route path="/index">index</Route> <Route path="/pos" render={()=>{ return this.state.isLogin ?<Pos></Pos> : <Login></Login> }}></Route> </Router> ) } }
第四例子 NavLink
- 一般用NavLink比較多
- 點中哪個鏈接,自動給加個 class="active"
- Link 標簽不會自動加的
import React, { Component } from 'react' import {BrowserRouter as Router,Route,NavLink,useLocation,Redirect,Switch} from 'react-router-dom' export default class index extends Component { render() { return ( <Router> <NavLink to="/index">首頁</NavLink> <NavLink to="/about">關于</NavLink> <hr/> <Route exact path="/index/" >index</Route> <Route path="/about/" >about</Route> </Router> ) } }
第五個例子 把a鏈接換成其他標簽
- NavLink /Link 生成的是a鏈接,在外邊在用li標簽包裹一下怎么做呢
import React, { Component } from 'react' import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom' //自定義鏈接 const CustomLink = (props)=>{ return <li> <NavLink to={props.path}>{props.name}</NavLink> </li> } export default class index extends Component { render() { return ( <Router> <CustomLink path="/index" name="首頁"></CustomLink> <NavLink to="/movie">電影</NavLink> <hr></hr> <Route path="/index">home</Route> <Route path="/movie">movie</Route> </Router> ) } }
第六個例子 編程式導航
- 不像vue那么簡單,一個函數搞定
- 這個是自己實現的
import React, { Component } from 'react' import './style.css' //這個就是定義激活active的樣式 import {BrowserRouter as Router,Route,Link,NavLink,Redirect,Switch} from 'react-router-dom' import City from './City' //這個就是自己定義的編程式導航 export default class index extends Component { render() { return ( <Router> <NavLink exact to="/">我的</NavLink> <City path="/city"></City> <hr></hr> <Route exact path="/">profile</Route> <Route path="/city">City</Route> </Router> ) } } //自己定義的編程式導航 City.js import React, { Component } from 'react' import {withRouter} from 'react-router-dom' class City extends Component { handleClick=()=>{ //編程式導航 this.props.history.push(this.props.path) } render() { return ( <li className={this.props.path == this.props.location.pathname? 'active':''} onClick={this.handleClick}>城市</li> ) } } //自定義鏈接是拿不到路由信息, 這個必須用withRouter這個高階函數增強一下,才能拿到路由相關的信息 export default withRouter(City)
第七個例子 用children函數來渲染組件
不管路由匹配上或者匹配不上,總會執行它
只不過區別是匹配上 props.match 不為空,否則為空
import React, { Component } from 'react' import './style.css' import {BrowserRouter as Router,Route,NavLink} from 'react-router-dom' export default class index extends Component { render() { return ( <Router> <NavLink exact to="/">我的</NavLink> <Route path="/setting" children={(props)=>{ return <NavLink to="/setting">設置</NavLink> }}></Route> <hr></hr> <Route exact path="/">profile</Route> <Route path="/setting">setting</Route> </Router> ) } }
安裝 React Developer Tools 調試工具
React Developer Tools - Chrome 擴展下載安裝地址
總結
理解React中虛擬DOM的概念
理解React中三種Diff算法的概念
使用JS中createElement的方式創建虛擬DOM
使用ReactDOM.render方法
使用JSX語法并理解其本質
掌握創建組件的兩種方式
理解有狀態組件和無狀態組件的本質區別
理解props和state的區別