## React 詳細(xì)文檔
1. React 特點:
聲明式編程:
(1) 聲明式編程現(xiàn)在是目前整個大前端開發(fā)模式: Vue React Flutter SwiftUI;
(2) 它允許我們只需要維護(hù)自己的狀態(tài),當(dāng)狀態(tài)改變時,React可以根據(jù)最新的狀態(tài)去渲染我們的UI界面
組件化開發(fā):
(1) 組件化開發(fā)頁面目前前端的流行趨勢, 我們會將復(fù)雜的界面拆分為一個個小組件
(2) 如何合理的進(jìn)行組件的劃分和設(shè)計也是后面我會講到的一個重點;
多平臺適配:
(1) 2013年,React 發(fā)布之處主要是開發(fā)Web 頁面;
(2) 2015年, Facebook推出了 ReactNative,用于開發(fā)移動端跨平臺(目前雖然Flutter非常火爆,但是還是有很多公司在使用ReactNative)
(3) 2017年,Facebook推出ReactVR,用于開發(fā)虛擬現(xiàn)實Web應(yīng)用程序,
2. React 開發(fā)依賴
React開發(fā)必須依賴三個庫
(1) React: 包含react所必須的核心代碼
(2) react-dom: react渲染在不同平臺所需要的核心代碼
(3) babel: 將jsx轉(zhuǎn)換成React代碼工具
babel是什么呢?
Babel,又名叫Babel.js
- 是目前前端使用非常廣泛的編輯器, 轉(zhuǎn)移器
- 比如當(dāng)下很多瀏覽器并不支持ES6的語法, 但是確實ES6的語法非常的簡潔和方便,我們開發(fā)時希望使用它
- 那么編寫源碼時我們就可以使用ES6來編譯, 之后通過Babel工具, 將ES6轉(zhuǎn)成大多數(shù)瀏覽器都支持的ES5語法
React和Babel的關(guān)系:
- 默認(rèn)情況下開發(fā)React其實可以不使用babel
- 但是目前提示我們自己使用React.createElement來編寫源代碼, 他編寫的代碼非常的繁瑣和可讀性差
- 那么我們就可以直接編寫jsx(JavaScript XML) 的語法,并且讓babel幫助我們轉(zhuǎn)換成React.createElement.
第一次接觸React 會被他繁瑣的依賴搞蒙,對于Vue 來說,我們只是依賴一個Vue.js文件即可,但是React居然要依賴三個庫
(4) 其實呢,這三個庫是各司其職的,目前就是讓每個庫單純做自己的事情
(5) 在React的0.14版本之前低沒有react-dom 這個概念的,所有的功能都包含在react里
(6) 為什么要進(jìn)行拆分? 原因就是react-native
(7) react包中 包含了 react和 react-native所共同擁有的核心代碼
react-dom 針對web和native所完成的事情不同
(8) web端: react-dom 會講究jsx 最終渲染成真是的DOM 顯示在瀏覽器中
(9) native端: react-dom 會將jsx 最終渲染成原生的控件 (比如Android中的Button,IOS中的UButton)
引入依賴
- 方式一: 直接CDN引入
- 方式二: 下載后,添加本地依賴
- 方式三: 通過npm 管理(腳手架后續(xù)使用)
ES6的class
在ES6之前,我們通過function來定義類,
認(rèn)識JSX
const element = <h2>Hello world</h2>;
ReactDOM.render(element, document.querySelector("#root"))
這段element變量的聲明右側(cè)賦值的標(biāo)簽語法是什么呢?
- 他不是一段字符串(因為沒有使用引號包裹),他看起來是一個HTML原生, 但是我們能在js 中直接給一個變量賦值html嗎?
- 其實是不可以的,如果我們講type="text/babel"去掉,name就會出現(xiàn)語法錯誤;
- 他到底是什么呢? 其實就是一段JSX的語法
JSX是什么?
- JSX是一種JavaScript的語法擴(kuò)展(eXtension),也在很多地方稱之為JavaScript XML,因為看起來就是一段XML語法;
- 他用于描述我們的UI界面, 并且其完成可以和JavaScript融合在一起使用
- 他不同于Vue 中的模塊語法,不需要專門學(xué)習(xí)模塊語法中的一些指令(v-for,v-if,v-else,v-bind)
為什么React選擇JSX
React認(rèn)為渲染邏輯本質(zhì)上與其他UI邏輯存在內(nèi)在的耦合
- 比如UI需要綁定事件(button,a原生等等)
- 比如UI中需要展示數(shù)據(jù)狀態(tài),在某些狀態(tài)發(fā)生改變時,又需要改變UI
他們之間是密不可分的,所以React沒有講標(biāo)記分離到不同的文件中,而是將他們組合到了一起,這個地方就是組件(component)
其實JSX就是嵌入到JavaScript中的一種結(jié)構(gòu)語法
JSX的書寫規(guī)范
- JSX的頂層只能有一個根元素, 所以很多時候會在外層包裹一個div原生
- 為了方便閱讀 通常在JSX的外層包裹一個小括號,這樣可以方便閱讀,并且JSX可以進(jìn)行轉(zhuǎn)換書寫
- JSX中的標(biāo)簽可以是單標(biāo)簽,也可以是雙標(biāo)簽
如果是單標(biāo)簽的話,必須要/> 結(jié)尾;
JSX 的使用
JSX的注釋寫法:{/注釋的語法結(jié)構(gòu)/}
JSX嵌入變量
(1) 情況一:當(dāng)為Number String Array 類型時, 可以直接顯示
(2) 情況二:當(dāng)變量是null,undefined,Boolean類型時,內(nèi)容為空
如果希望可以顯示null,undefined,Boolean,那么需要轉(zhuǎn)換成字符串;
轉(zhuǎn)換的方式很多,比如toString方法,和空字符串拼接,String(變量)等方式
(3) 情況三:對象類型不能作為子元素(not valid as React child)JSX 嵌入表達(dá)式
(1) 運算表達(dá)式
(2) 三元運算符
(3) 執(zhí)行一個函數(shù)
JSX綁定屬性
- 比如元素都會有title屬性
- 比如元素img會有src屬性
- 比如a元素會有href屬性
- 比如元素可能會綁定calss
- 比如原生使用內(nèi)聯(lián)樣式style
JSX的本質(zhì)
實際上,JSX僅僅是React.createElement(component,props,...children)函數(shù)的語法糖.
所有的JSX最終都會轉(zhuǎn)換為React.createElement的函數(shù)調(diào)用.
-
createElement需要傳遞三個參數(shù)
-- 參數(shù)一:type
當(dāng)前的ReactElement的類型;
如果是標(biāo)簽元素, 那么就是用字符串表示"div";
如果是組件元素,那么就直接使用組件的名稱;--參數(shù)二:config
所有的JSX中的屬性都在config中以對象的屬性和值的形式儲存--參數(shù)三:children
存放在標(biāo)簽中的內(nèi)容,以children數(shù)組的方式進(jìn)行儲存
當(dāng)然,如果是多個元素,React內(nèi)部有對它們進(jìn)行處理,
虛擬DOM的創(chuàng)建過程
我們通過React.createElement最終創(chuàng)建出來一個ReactElement對象
這個ReactElement對象是什么作用呢? React為什么要創(chuàng)建它呢?
-- 原因是React利用ReactElement對象組成了一個JavaScript的對象樹
-- JavaScript的對象樹就是大名鼎鼎的虛擬DOM(Virtual DOM)為什么使用虛擬DOM
-- 很難跟蹤狀態(tài)的改變: 原有的開發(fā)模式,我們很難跟蹤到狀態(tài)的改變,不方便針對我們的應(yīng)用程序進(jìn)行調(diào)試,
-- 操作真是DOM性能較低:傳統(tǒng)的開發(fā)模式進(jìn)行頻繁的DOM操作,而這一做法性能非常的低;DOM操作性能非常低
-- 首先,document.createElement本身創(chuàng)建出來的就是一個非常的復(fù)雜的對象
http://developer.mozilla.org/zh-CN/docs/Web/API/Document?createElement
-- 其次,DOM操作會引起瀏覽器的回流和重繪,所以在開發(fā)中應(yīng)該避免頻繁的DOM操作聲明式編程
<1> 虛擬DOM幫助我們從命令式編程到了聲明式編程的模式
<2> React官方說法: Virtual DOM 是一種編程理念,
-- 在這個理念中,UI以一種理想話或者說虛擬話的方式保存在內(nèi)存中,并且它是一個相對簡單的JavaScript對象
-- 我們可以通過ReactDOM.render() 讓虛擬DOM和真實DOM同步起來, 這個過程中叫做協(xié)調(diào)(Reconciliation)
前端腳手架
對于現(xiàn)在比較流行的三大框架都有屬于自己的腳手架
--Vue的腳手架:vue-cli
--Angular的腳手架:angular-cli
--React的腳手架:create-react-app他們的作用都是幫助我們生成一個通用的目錄結(jié)構(gòu),并且已經(jīng)將我們所需的工程環(huán)境配置好
使用這些腳手架需要依賴什么呢?
--目前這些腳手架都是使用node編寫的,并且都是基于webpack的;
--所以必須在自己的電腦上安裝node環(huán)境Yarn 和 npm 命令的對比
Npm
npm install
npm install[package]
npm install --save[package]
npm install --save-dev[package]
npm rebuild
npm uninstall[package]
npm uninstall --save[package]
npm uninstall --save-dev[package]
npm uninstall --save-optional[package]
npm cache clean
rm-rf node_modules && npm install
Yarn
yarn install
yarn add[package]
yarn add[package]
yarn add[package][--dev/-D]
yarn install --force
yarn remove[package]
yarn remove[package]
yarn remove[package]
yarn remove[package]
yarn cache clean
yarn upgrade
-
React 安裝腳手架 命令如下:
--0.在國內(nèi),某些情況使用npm和yarn可能無法正常的安裝一個庫,這個時候我們就可以選擇使用cnpm
--CNPM安裝命令: npm install -g cnpm --registry=https://registry.npm.taobao.org--1.0 NodeJs的安裝網(wǎng)址: https://nodejs.org/en/download
--1.2 查看node 是否安裝成功: node --version
--1.3.yarn安裝命令:npm install -g yarn
查看yarn/npm 的版本是否安裝成功: yarn/npm --version--1.4.React 項目的腳手架安裝命令: npm install -g create-react-app
查看版本命令: create-react-app --version--1.5.創(chuàng)建React項目的命令:
// 創(chuàng)建方式一:
create-react-app 項目名稱(項目名稱,不能寫大寫字母)--1.6. 項目創(chuàng)建好了以后:
cd 02_learn_scaffold
yarn start (類似于Vue的npm run serve)
需要使用yarn安裝各類依賴的話:
yarn add axios
React 項目結(jié)構(gòu) 詳細(xì)解說
1. node_modules: 所有依賴的總集合包,和vue的是一樣的
2. public {
favicon.ico:圖標(biāo)
index.html:每個項目的入口,單頁面復(fù)應(yīng)用
manifest.json: 和web app配置相關(guān)
logo192.png:圖片而已
robots.txt:設(shè)置爬蟲規(guī)則的
}
3. src { // 寫的所有的源代碼文件的
App.css: 當(dāng)前的App組件的 css 樣式
App.js:App組件的代碼文件(函數(shù)式組件)
App.test.js: 對App寫一些測試用例的
index.css: 寫全局樣式的
index.js: 整個應(yīng)用程序的入口js文件
logo.svg: 項目剛啟動時看到的當(dāng)前頁面旋轉(zhuǎn)的那個SVG圖片
reportWebVitals.js 默認(rèn)幫我們寫好的注冊PWA相關(guān)代碼
setupTests.js:測試初始化文件
}
4. .gitignore(這個文件的主要工作是:忽略一些不需要提交到代碼倉庫的文件就在這里寫,不需要共享的文件寫在這里)
5. package.json(關(guān)于我們整個項目管理配置的一個文件)
6. README.md 說明文檔
7. yarn.lock (記錄真實版本的依賴)
當(dāng)我們創(chuàng)建好了腳手架以后 需要把 Src 文件夾中的所有的文件全部刪掉
- 創(chuàng)建 index.js 文件 里面需要寫以下代碼
// 第一步:
import React from 'react';
//第二步:
import ReactDOM from "react-dom"
// 導(dǎo)入你封裝的 js 文件
import { sum } from "./utils"
console.log(sum(10, 20));
// 第三步
class App extends React.Component {
constructor() {
super();
this.state = {
count: 0
}
}
render() {
return (
<div>
<h2>當(dāng)前計數(shù)</h2>
<button>+</button>
<button>-</button>
</div>
)
}
}
// 第三步:
// ReactDOM.render(需要掛載的組件名稱, 這個地方會找到你的pubic 里面的 index.html中的 <div id="root"></div> 文件)
ReactDOM.render(<App/>, document.querySelector('#root'))
這種寫到 index.js 中是不規(guī)范的,所以 要重新寫一個 App.js 文件 把 第三步 抽取到 App.js 文件中
實際代碼截圖:

PWA
- PWA全稱Progressive Web App,即漸進(jìn)式WEB應(yīng)用
- 一個PWA應(yīng)用首先是一個網(wǎng)頁,可以通過Web技術(shù)編寫出一個網(wǎng)頁應(yīng)用
- 隨后添加上 App Manifest 和 Service Worker 來實現(xiàn)PWA的安裝和離線等功能
- 這種Web存在的形式,我們稱為Web App
PWA 解決了哪些問題呢?
- 可以添加到主屏幕,點擊主屏幕圖標(biāo)可以實現(xiàn)啟動動畫以及隱藏地址欄
- 實現(xiàn)離線緩存功能,即使用戶手機沒有網(wǎng)絡(luò),依然可以使用一些離線功能
- 實現(xiàn)了消息推送
- 等等一系列類似于Native App 相關(guān)功能
webpack 是什么 ?
webpack是一個現(xiàn)代化JavaScript 應(yīng)用程序的靜態(tài)模塊打包器
當(dāng)webpack 處理 應(yīng)用程序時,他會遞歸構(gòu)建一個依賴關(guān)系圖,其中包含應(yīng)用程序需要的每個模塊 然后將所有這些模塊打包成一個或多個bundle;
-
想要暴露出webpack的 配置顯示在文件中的話 可以執(zhí)行命令:
-- yarn eject(1) 如果你要是在腳手架創(chuàng)建好了以后的話,去修改項目中的文件 會出現(xiàn) 一個提示,
你就要執(zhí)行 一下的命令:-- git add .
-- git commit -m "代碼修改"
分而治之的思想 -- 也就是組件化開發(fā)
/********* 類組件 start ******************/
類組件的定義有如下要求:
-- 組件的名稱是大寫字符開頭(無論是類組件還是函數(shù)組件)
-- 類組件需要繼承自 React.Component
-- 類組件必須實現(xiàn)render函數(shù)-
在ES6之前,可以通過create-react-class 模塊來定義類組件,但是目前官網(wǎng)建議我們使用ES6的class類定義
使用class定義組件:
-- constructor是可選的,我們通常在constructor中初始化一些數(shù)據(jù)
-- this.state中維護(hù)的就是我們組件內(nèi)部的數(shù)據(jù)
-- render() 方法是class組件中唯一必須實現(xiàn)的方法 render函數(shù)的返回值
(1) 當(dāng)render 被調(diào)用時,他會檢查 this.props 和 this.state的變化并返回以下類型之一
(2) React 元素:
- 通常通過JSX 創(chuàng)建
- 例如 <div /> 會被React 渲染為DOM 節(jié)點, <MyComponent /> 會被React傳染為自定義組件;
- 無論是<div /> 還是<MyComponent /> 均為React 元素
定義React 代碼塊的 快捷方式: rcc(類組件)/rfc(函數(shù)組件)
如果你不想返回一個根組件的話, 數(shù)組或者 fragments: 使得render 方法可以返回多個元素
那么可以這種寫:
import React,{Component} from 'react';
import React, { Component } from 'react';
class App extends Component {
render() {
return (
[
<div>Hello World</div>
<div>Hello React</div>
]
);
}
}
export default App;
Portals:可以渲染子節(jié)點 到不同的DOM子樹中
字符串或數(shù)值類型:他們在DOM 中會被渲染為文本節(jié)點
布爾類型或 null: 什么都不渲染
/********* 類組件 end ******************/
/********* 函數(shù)組件 start ******************/
函數(shù)組件是使用function來進(jìn)行定義的函數(shù),只是這個函數(shù)會返回和類組件中的render函數(shù)返回一樣的內(nèi)容
-
函數(shù)組件有自己的特點
-- 沒有生命周期,也會被更新并掛載,但是沒有生命周期函數(shù)
-- 沒有this(組件實例)
-- 沒有內(nèi)部狀態(tài)(state)
/********* 函數(shù)組件 end ******************/
認(rèn)識生命周期
生命周期和生命周期的關(guān)系:
--生命周期是一個抽象的概念,在生命周期的整個過程,分成了很多的階段
(1) 比如掛載階段(Mounting),組件第一次在DOM樹中被渲染的過程
在掛載階段要執(zhí)行的生命周期函數(shù):
constructor 函數(shù)
render(){}
React updates DOM and refs
componentDidMount (已經(jīng)掛載成功)
(2) 比如在更新過程(Updataing),組件狀態(tài)發(fā)生變化,重新更新渲染的過程
New props setStete() forceUpdate()
render() {}
React updates DOM and refs
componentDidUpdate (已經(jīng)發(fā)生更新)
(3) 比如卸載過程(Unmounting),組件從DOM樹中被移除的過程
Unmounting
componentWillUnmount (即將被移除掉)React內(nèi)部為了告訴我們當(dāng)前處于那些階段,會對我們組件內(nèi)部實現(xiàn)的某些函數(shù)進(jìn)行回調(diào), 這個就是函數(shù)的生命周期函數(shù)
(1)比如實現(xiàn)componentDidMount函數(shù): 組件已經(jīng)掛載到DOM上時,就會回調(diào)
(1.1) componentDidMoun() 會在組件掛載后(插入DOM樹中)立即調(diào)用
--依賴DOM的操作可以在這里進(jìn)行
--在此處發(fā)送網(wǎng)絡(luò)請求就是最好的地方
--可以在此處天機一些訂閱(會在componentWillUnmount取消訂閱)
(2)比如實現(xiàn)componentDidUpdate函數(shù): 組件已經(jīng)發(fā)生了更新時,就會回調(diào)
(2.1) componentDidUpdate函數(shù): 組件已經(jīng)發(fā)生了更新時,就會回調(diào),首次渲染不會執(zhí)行此方法
-- 當(dāng)組件更新后,可以在此處對DOM進(jìn)行操作
-- 如果你對更新前后的props進(jìn)行了比較,也可以選擇此處進(jìn)行網(wǎng)絡(luò)請求;(例如props未發(fā)生變化時,則不會執(zhí)行網(wǎng)絡(luò)請求)
(3)比如實現(xiàn)componentWillUnmount函數(shù): 組件即將被移除,就會回調(diào)
(3.1) componentWillUnmount() 會在組件的卸載以及銷毀之前直接調(diào)用的
-- 在此方法中執(zhí)行必要的清理操作
-- 例如,清楚timer,取消網(wǎng)路請求或清除,在componentWillUnmount中創(chuàng)建的訂閱等
- 我們談React 聲明周期時,主要談的是類的生命周期, 因為函數(shù)式組件是沒有生命周期函數(shù)的;(后面我們可以通過hooks來模擬一些生命周期的回調(diào))
3.1. getDerrivedStateFromProps的使用 -- (如果當(dāng)前的construtor中的state里面的數(shù)據(jù)永遠(yuǎn)依賴別的組件傳遞過來的,如果你同時希望發(fā)生變化的話那么你就可以使用這個函數(shù)來同步)
3.2 shouldComponentUpdate的使用 --(這個是決定我們的render函數(shù)到底需不需要渲染)
3.3 getSnapshotBeforeUpdate的使用 -- (這個可以獲取你更新之前的一些數(shù)據(jù)的)
-
Constructor
(1)如果不初始化state或不進(jìn)行方法綁定,則不需要為React組件實現(xiàn)構(gòu)造函數(shù)(2) Constructor
-- 通過this.state賦值對象來初始化內(nèi)部的state
-- 為事件綁定實例( this )
認(rèn)識組件的嵌套
import React, { Component } from 'react';
// Header
function Header() {
return <h2> 我是Header </h2>
}
// main
// main 里面還有細(xì)節(jié)的拆分
function Banner() {
return <h2> 我是輪播圖組件 </h2>
}
function Products() {
return (
<div>
<ul>
<li>商品列表1</li>
<li>商品列表2</li>
<li>商品列表3</li>
<li>商品列表4</li>
<li>商品列表5</li>
<li>商品列表6</li>
</ul>
</div>
)
}
function Main() {
return (
<div>
<h2>我是Main組件</h2>
<Banner />
<Products />
</div>
)
}
// Footer
function Footer() {
return <h2> 我是Footer組件 </h2>
}
class App extends Component {
render() {
return (
<div>
<Header />
<Main />
<Footer />
</div>
);
}
}
export default App;
組件之間的通訊
<一> 父組件傳子組件:
父組件通過 屬性 = 值的形式來傳遞給子組件數(shù)據(jù);
子組件通過props參數(shù)獲取父組件傳遞過來的數(shù)據(jù)
-
參數(shù)的驗證 對于傳遞子組件的數(shù)據(jù), 有的時候我們可能希望進(jìn)行驗證, 特別是對于大型項目來說
-- 當(dāng)然,如果你的項目中默認(rèn)集成了Flow或者TypeScript,那么直接就可以進(jìn)行類型驗證
-- 但是即使我們沒有使用 Flow或者TypeScript,也是可以通過prop-types庫來進(jìn)行參數(shù)驗證-- 從React v15.5開始, React.propTypes已移入另外一個包中:prop-types庫
<二>子傳父
- 在React中同樣的是通過 props 傳遞消息,只是讓父組件給子組件傳遞一個回調(diào)函數(shù),在子組件中調(diào)用這個函數(shù)即可
<三>非父子組件的傳遞
-
React.createContext
-- 創(chuàng)建一個需要共享的Context對象
-- 如果一個組件訂閱了Context,那么這個組件會從離自身最近的那個匹配的Provider中讀取到當(dāng)前的context值;
-- defaultValue是組件在頂層查找過程中沒有找到對應(yīng)的Provider,那么就使用默認(rèn)值const MyContext = React.createContext(defaultValue) Context.Provider
--每個Context對象都會返回一個Provider React組件,它允許消費組件訂閱context 的變化
--Provider接收一個value屬性,傳遞給消費組件
--一個Provider可以和多個消費組件有對應(yīng)關(guān)系;
--多個Provider也可以嵌套使用,里層覆蓋外層的數(shù)據(jù)
--當(dāng)Provider的value值發(fā)生變化時,它內(nèi)部的所有消費組件都會被重新渲染class.contextType
--掛載在class上的contextType屬性會被重新賦值為一個由React.createContext() 創(chuàng)建Context對象
--這能讓你使用this.context來消費Context上的那個值
--你可以在任何的生命周期訪問他,包括在render函數(shù)中也是可以的UserContext.Consumer
-- 函數(shù)式的專用語法:
<UserContext.Consumer>
{
value => {
return (
<div>
<h2>用戶昵稱: {value.nikename}</h2>
<h2>用戶等級: {value.level}</h2>
</div>
)
}
}
</UserContext.Consumer>
為什么使用setState
因為修改了 state之后,希望React根據(jù)最新的State來重新渲染界面,但是這種方式的修改React并不知道數(shù)據(jù)發(fā)生變化,
React并沒有實現(xiàn)類似于Vue的Object.defineProtype或者Vue3的Proxy的方式監(jiān)聽數(shù)據(jù)的變化
我們必須通過setState的方法告知數(shù)據(jù)已經(jīng)發(fā)生變化了
在組建中并沒有實現(xiàn)setState的方法,為什么可以調(diào)用了?
--原因很簡單,setState方法是從Component中繼承過來的setSate 這個是一個異步的更新
-
為什么setState 設(shè)計為異步?
-- https://github.com/facebook/react/issues/11527#issuecomment-360199710--總結(jié): 1. setState設(shè)計為異步,可以顯著的提升性能; 2. 如果每次調(diào)用setState都進(jìn)行一次更新,那么意味著render函數(shù)會被繁瑣調(diào)用,界面重新渲染,這樣效率很低; 3. 最好的辦法應(yīng)該是獲取到多個更新,之后進(jìn)行批量的更新 4. 如果同步更新了state,但是還沒有執(zhí)行render函數(shù),那么state和props不能保持同步 5. state和props不能保持一致性,會在開發(fā)中產(chǎn)生很多問題 PS: setState在那些情況是同步那些是異步呢? -- 在組件生命周期或React合成時間中,setState是異步; -- 在setTimeout或者原生dom事件中,setState是同步
React更新機制
-- 我們在前面已經(jīng)學(xué)習(xí)React 的渲染流程
JSX ---> 虛擬DOM --->真是DOM
-- 那么React的更新流程呢?
Props/state改變 --> render函數(shù)重新執(zhí)行 --> 產(chǎn)生新的DOM樹-->新舊DOM樹進(jìn)行diff -->計算出差異進(jìn)行更新-->更新到真實的DOM上
- 情況一: 當(dāng)節(jié)點為不同的元素,React會拆卸原有的樹,并且簡歷起新的樹;
-- 當(dāng)一個元素從<a> 變?yōu)?lt;img>,從<Article>變?yōu)?lt;Comment>,都會觸發(fā)一個完整的重建流程
-- 當(dāng)卸載一顆樹時,對應(yīng)的DOM節(jié)點也會被銷毀,組件實例將執(zhí)行componentWillUnmount() 方法
-- 當(dāng)簡歷一顆新的樹時,對應(yīng)的DOM節(jié)點會創(chuàng)建以及插入到DOM中,組件實例將執(zhí)行componentWillMount() 方法,緊接著componentDidMount()方法
比如下面代碼更改:
React會銷毀Counter組件并且重新裝載一個新的組件,而不會對Counter進(jìn)行復(fù)用
<div>
<Counter />
</div>
<span>
<Counter />
</span>
- 情況二:對比同一類型的元素
-- 當(dāng)對比兩個相同的類型的React元素時,React會保留DOM節(jié)點,僅比對及更新與改變的屬性
-- 比如下面的代碼更改
通過比對這兩個元素,React知道需要修改DOM元素上的className;
<div className="before" title="stuff"></div>
<div className="after" title="stuff"></div>
比如下面的代碼更改:
當(dāng)更新style屬性時,React僅更新有所更變的屬性
通過比對這兩個元素時,React知道只需要修改DOM元素上的color樣式,無需修改fontWeight.
如果是同類型的組件元素:
--組件會保持不變,React會更新改組件的props,并且調(diào)用componentWillReceiveProps()和componentWillUpdate()方法
--下一步,調(diào)用render()方法,diff算法會將在之前的結(jié)果以及新的結(jié)果進(jìn)行遞歸情況三:對子節(jié)點進(jìn)行遞歸
--在默認(rèn)條件下,當(dāng)遞歸DOM節(jié)點的子元素時,React會同時遍歷兩個子元素的列表,當(dāng)產(chǎn)生差異時,生成一個mutation
--前面兩個比較時完全相同的,所以不會產(chǎn)生mutation;
--左后一個比較,產(chǎn)生一個mutation,將其插入到新的DOM樹中即可
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
- 但是如果我們是在中間插入一條數(shù)據(jù):
React會對每個子元素一個mutation,而不是保持<li>星際穿越</li> 和<li>盜墓空間</li>的不變,這種低效的比較方式會帶來一定的性能問題
<ul>
<li>星際穿越</li>
<li>盜墓空間</li>
</ul>
<ul>
<li>大話西游</li>
<li>星際穿越</li>
<li>盜墓空間</li>
</ul>
keys的優(yōu)化
我們在前面的遍歷列表時,總會提示一個警告,讓我們加入key屬性
方式一: 在最后面插入數(shù)據(jù)
這種情況,有無key意義并不大方式二: 在前面插入數(shù)據(jù)
這種做法,在沒有key 的情況下,所有的li 都需要進(jìn)行修改當(dāng)子元素(這里的li) 擁有key時,React使用key為111 和 222的元素僅僅進(jìn)行移位,不需要進(jìn)行任何的修改
將key為333 的元素插入到最前面的位置即可
- key的注意事項:
-- key應(yīng)該是唯一的
-- key不要使用隨機數(shù)(隨機數(shù)在下一次render時,會重新生成一個數(shù)字)
-- 使用index作為可以,對性能是沒有優(yōu)化的
render 函數(shù)的調(diào)用
- 當(dāng)你有很多的組件都嵌套在App里面的時候 例如:
import React, { Component } from 'react';
class Banner extends Component {
render() {
return <h2>我是中間部分</h2>
}
}
class HeaderBox extends Component {
render () {
return (
<div>我是頭部標(biāo)簽</div>
)
}
}
function Main() {
return (
<div>
<HeaderBox/>
<Banner/>
</div>
)
}
class App extends Component {
render() {
return (
<div>
<Main/>
</div>
);
}
}
export default App;
這樣的話就會你每次在點擊button + 1 的時候 他會把所有的組件都會重新的渲染一遍,這樣很影響性能, 所以就要用shouldComponentUpdata這個生命周期函數(shù)
PS: 這個生命周期不是隨便亂用的,這個是當(dāng)你需要阻斷更新的地方你就阻斷,不應(yīng)該阻斷的地方你就不要阻斷
shouldComponentUpdate生命周期的使用--這個是類特有的生命周期:
React 給我們提供一個生命周期方法 shouldComponentUpdate(很多時候簡稱SCU),這個方法接受參數(shù),并且需要有返回值
該方法有兩個參數(shù)
-- 參數(shù)一:nextProps修改之后,最新的props屬性
-- 參數(shù)二:nextState修改之后,最新的state屬性該方法返回的是一個Boolean類型
-- 返回值為true,那么就需要調(diào)用render方法
-- 返回值為false,那么就不需要調(diào)用render方法
-- 默認(rèn)返回的是true,也就是只要state發(fā)生了變化,就會調(diào)用render方法比如我們在App中增加一個message屬性
--jsx中沒有依賴這個message,那么他的改變不應(yīng)該引起重新渲染
-- 但是因為render監(jiān)聽到state的改變,就會重新render,所以最后render方法還是被重新調(diào)用了
如果每個組件都有這種情況的話,那是不是每個組件中都要寫shoundComponentUpdate呢?
不是的,你只需要繼承PureComponent 這個就可以解決所有的問題 (這個僅限用于類組件上面的,函數(shù)式組件是不行的需要用到 memo)
PureComponent 的使用
import React, { Component } from 'react';
// 所以不管你里面有多少組件 就不需要寫shoundComponentUpdate生命周期了
// 也不需要在生命周期中去寫判斷了
// 直接去讓你的組件去繼承 PureComponent 這個就行了
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0,
message: "Hello,world"
}
}
// 這樣的話就會你每次在點擊button + 1 的時候 他會把所有的組件都會重新的渲染一遍,
// 這樣很影響性能, 所以就要用shouldComponentUpdata這個生命周期函數(shù)
// 這個生命周期不是隨便亂用的,這個是當(dāng)你需要阻斷更新的地方你就阻斷,
// 不應(yīng)該阻斷的地方你就不要阻斷,所以這個地方你要做判斷
// shouldComponentUpdate(nextProps,nextState) // 里面會有2個參數(shù)的
// 最新的 nextProps 和最新的 nextState
// 如果還有其他的需要通過的那么你就需要繼續(xù)在下面判斷了
shouldComponentUpdate(nextProps, nextState) {
// console.log(nextProps);
// console.log(nextState);
// return true // 只要這個地方寫true的話就是所有的組件需要更新
// return false // 這個就不會更新了
if (this.state.count !== nextState.count) {
return true
}
return false
}
render() {
console.log("App render函數(shù)調(diào)用");
return (
<div>
<h2>當(dāng)前計數(shù): {this.state.count}</h2>
<button onClick={e => this.addNumber()}>+ 1</button>
<button onClick={e => this.changeText()}>改變文本</button>
</div>
);
}
addNumber() {
this.setState({
count: this.state.count + 1
})
}
changeText() {
this.setState({
message: "哈哈哈"
})
}
}
export default App;
PS: 所以不管你里面有多少組件 就不需要寫shoundComponentUpdate生命周期了,也不需要在生命周期中去寫判斷了,
直接去讓你的組件去繼承 PureComponent 這個就行了 (這個僅限用于類組件上面的,函數(shù)式組件是不行的需要用到 memo)
memo的使用:
// 1.導(dǎo)入memo 高階組件
import React, { PureComponent,memo } from 'react';
class Header extends PureComponent {
render() {
return <h2> 我是頭部標(biāo)簽 </h2>
}
}
// 2. 使用 memo
const MemoHeader = memo(function Products () {
return (
<div>
<ul>
<li>我是商品列表一</li>
<li>我是商品列表二</li>
<li>我是商品列表三</li>
<li>我是商品列表四</li>
<li>我是商品列表五</li>
</ul>
</div>
)
})
function Main() {
return (
<div>
<Header/>
{/* 掛載memo */}
<MemoHeader/>
</div>
)
}
class App extends PureComponent {
render() {
return (
<div>
<Main/>
</div>
);
}
}
export default App;
setState傳遞的數(shù)據(jù)需要不可變的數(shù)據(jù)
import React, { PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
nameList: [
{ name: "lilei", age: 20 },
{ name: "hanmeimei", age: 28 },
{ name: "chuchuhu", age: 25 },
]
}
}
// shouldComponentUpdate(newProps, newState) {
// if (newState.nameList !== this.state.nameList) {
// return true
// }
// return false
// }
render() {
return (
<div>
<h2>好友列表</h2>
<ul>
{
this.state.nameList.map((item, index) => {
return (
<li key={index}>
姓名:{item.name}
年紀(jì):{item.age}
<button onClick={e => this.addNum(index)}>年齡+1</button>
</li>
)
})
}
</ul>
<button onClick={e => this.addData()}>添加數(shù)據(jù)</button>
</div>
);
}
addData() {
// const newData = { name: "tom", age: 30 }
// this.state.nameList.push(newData)
// this.setState({
// nameList:this.state.nameList
// })
// 推薦做法
const newNameList = [...this.state.nameList]
newNameList.push({ name: "tom", age: 28 })
this.setState({
nameList: newNameList
})
}
addNum(index) {
const newNames = [...this.state.nameList]
const addNewAge = newNames[index].age += 1
this.setState({
age:addNewAge
})
}
}
export default App;
全局事件 events 兄弟之間的傳遞
- 需求 當(dāng)我在這個組件中點擊按鈕
向上面的Home 組件傳遞一條數(shù)據(jù)過去
然后在Home組件中展示出來
解決方案:
需要下載一個第三方庫 events --- > yarn add events
import React, { PureComponent } from 'react';
// 1.EventEmitter(事件發(fā)射)--> 這個是一個類
import { EventEmitter } from "events"
// 2.創(chuàng)建事件總線: event bus
// 像這樣的文件你是可以單獨的存在一個全局的文件中的
// 然后你再導(dǎo)入到你需要的文件中
const eventBus = new EventEmitter();
class Home extends PureComponent {
// 5. 要想監(jiān)聽的話 你要在組件的生命周期里面去監(jiān)聽
// componentDidMount(){} 要在掛載階段去監(jiān)聽你的事件
// componentWillUnmount() {} 要在卸載過程去下載你的事件
componentDidMount() {
/**
* eventBus.addListener()方法里面是有2個參數(shù)的
*
* 第一個參數(shù)是:監(jiān)聽兄弟組件傳遞過來的事件
* 第二個參數(shù)是: 一個回調(diào)函數(shù)
*
* **/
// eventBus.addListener('sayHello',(...args)=>{
// console.log(args);
// })
eventBus.addListener('sayHello',this.handleSayHelloListenner)
}
componentWillUnmount() {
// 6. 當(dāng)組件即將小時的時候 你應(yīng)當(dāng)清楚監(jiān)聽事件
// eventBus.removeListener()清除事件名稱
eventBus.removeListener("sayHello",this.handleSayHelloListenner)
}
// 單獨定義這個事件
handleSayHelloListenner(...args) {
console.log(...args);
}
render() {
return (
<div>
Home
</div>
)
}
}
// 需求 當(dāng)我在這個組件中點擊按鈕
// 向上面的Home 組件傳遞一條數(shù)據(jù)過去
// 然后在Home組件中展示出來
// 解決方案:
// 需要下載一個第三方庫 events --- > yarn add events
class Profile extends PureComponent {
render() {
return (
<div>
Profile
{/* 3.發(fā)射一個事件出去 */}
<button onClick={e => this.emitEvent()}>點擊了Profile按鈕</button>
</div>
)
}
emitEvent() {
// 上面全局定義的一個eventBus對象 他new出來的有個事件叫emit
// 4. emit() 方法里面 第一個參數(shù)是:事件名稱,第二個是: 傳遞的參數(shù)
eventBus.emit("sayHello", 'hello,world', 123322)
}
}
class App extends PureComponent {
render() {
return (
<div>
<Home />
<Profile />
</div>
);
}
}
export default App;
事件總線 總結(jié):
- 前端通過Context主要實現(xiàn)的是數(shù)據(jù)的共享, 但是在開發(fā)中如果有跨組件之間的時間傳遞,應(yīng)該如何操作呢?
(1) 在Vue中我們可以通過Vue的實例,快速實現(xiàn)一個事件總線(eventBus),來完成操作
(2) 在React中,我們可以依賴一個使用較多的庫events來完成對應(yīng)的操作
(3) 我們可以通過npm 或者yarn來安裝events
yarn add events
(4) events常用的API:
-- 創(chuàng)建EventEmitter對象: eventBus對象
const eventBus = new EventEmitter()
-- 發(fā)射事件: eventBus.emit("事件名稱",參數(shù)列表)
eventBus.emit("sayHello", 'hello,world', 123322)
-- 監(jiān)聽事件:events.addListener("事件監(jiān)聽",監(jiān)聽函數(shù))
eventBus.addListener('sayHello',this.handleSayHelloListenner)
-- 移除事件:eventBus.removeListener("事件監(jiān)聽",監(jiān)聽函數(shù))
eventBus.removeListener(sayHello",this.handleSayHelloListenner)
如何使用ref
- 在React的開發(fā)模式中,通常項情況下不需要,也不建議直接去操作DOM原生,但是某些特殊情況,確實需要獲取到DOM進(jìn)行某些操作
(1) 管理焦點,文本選擇或媒體播放
(2) 觸發(fā)強制動畫
(3) 繼承第三方DOM庫
- 如何創(chuàng)建refs來獲取對應(yīng)的DOM呢? 目前是有三種方式:
(1) 方式一: 傳入字符串
-- 使用時通過this.refs. 傳入的字符串格式獲取對應(yīng)的元素
import React, { PureComponent } from 'react';
class App extends PureComponent {
componentDidMount() {
// document.getElementById() // 一般不推薦
}
render() {
return (
<div>
{/* 1.方式一: ref=字符串/對象/函數(shù) */}
<h2 ref='titleRef'>React,Hello</h2>
<button onClick={e=> this.changeText()}>改變文本</button>
</div>
);
}
changeText() {
// 2.
// console.log(this.refs.titleRef);
this.refs.titleRef.innerHTML = "哈哈哈哈"
}
}
export default App;
(2) 方式二: 傳入一個對象
-- 對象是通過React.createRef() 方式創(chuàng)建出來的
-- 使用時獲取到創(chuàng)建的對象其中有一個current屬性就是對應(yīng)的元素
// 1.導(dǎo)入一個 createRef
import React, { createRef, PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
// 2.第二步:
this.titleRef = createRef();
}
render() {
return (
<div>
{/* 3. 使用ref=對象(ref={this.titleRef}) */}
<h2 ref={this.titleRef}>React,Hello</h2>
<button onClick={e => this.changeText()}>改變文本</button>
</div>
);
}
changeText() {
// 4.對象的使用方式:
console.log(this.titleRef); // {current:h2}
this.titleRef.current.innerHTML = "Hello,JavaScript"
}
}
export default App;
(3) 方式三:傳入一個函數(shù)
-- 該函數(shù)會在DOM被掛載時進(jìn)行回調(diào), 這個函數(shù)會傳入一個 元素對象,我們可以自己保存;
-- 使用時,直接拿到之前保存的元素對象即可
import React, { PureComponent } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.titleRef = null
}
render() {
return (
<div>
{/* 1.傳入一個函數(shù)基本語法:ref={arg =>this.titleRef = arg */}
<h2 ref={arg =>this.titleRef = arg}>React,Hello</h2>
<button onClick={e => this.changeText()}>改變文本</button>
</div>
);
}
changeText() {
// 2.
console.log(this.titleRef);
this.titleRef.innerHTML = "Hello,TypeScript"
}
}
export default App;
refs 的類型
- ref 的值根據(jù)節(jié)點的類型而有所不同
-- 當(dāng)ref屬性用于HTML元素時.構(gòu)造函數(shù)中使用React.createRef()創(chuàng)建的ref接收底層DOM元素作為DOM 元素作為其current屬性
-- 當(dāng)ref屬性用于自定義class組件是,ref對象接收組價的掛載實例作為其current屬性
-- 你不能在函數(shù)組件上使用ref屬性.因為他們沒有實的 - 函數(shù)式組件是沒有實例的,所以無法通過ref獲取他們的實例
-- 但是某些時候,我們可能想要獲取函數(shù)式組件中的某個DOM元素
-- 這個時候我們可以通過React.forwardRef,后面我們也會學(xué)習(xí)hooks中如何使用ref
認(rèn)識受控組件
在React中,HTML表單的處理方式和普通的DOM元素不太一樣,表單元素通常會保存在一些內(nèi)部的state
比如下面的HTML表單元素
-- 這個處理方式是DOM默認(rèn)的處理HTML表單的行為, 在用戶點擊提交時會提交到某個服務(wù)器中, 并且刷新頁面
-- 在React中,并沒有禁止這個行為,他依然是有效的
-- 但是嘗嘗情況下使用JavaScript函數(shù)來方便的處理表單提交,同時還可以訪問影虎填寫的表單數(shù)據(jù)
-- 實現(xiàn)這種效果的標(biāo)準(zhǔn)方式是使用 "受控組件"
<form>
<label>
名字:
<input type="text" name="name">
</label>
<input type="submit" value="提交">
</form>
非受控組件
import React, { PureComponent,createRef } from 'react';
class App extends PureComponent {
constructor(props) {
super(props)
this.usernameRef = createRef()
}
render() {
return (
<div>
<form onSubmit={e => this.handleSubmit(e)}>
<label htmlFor="username">
用戶:
<input type="text" id="username" ref={this.usernameRef} />
</label>
<input type="submit" value="提交"/>
</form>
</div>
);
}
handleSubmit(e) {
e.preventDefault()
console.log(this.usernameRef.current.value);
}
}
export default App;
認(rèn)識高階函數(shù)
認(rèn)識高階組件
高階組件的英文名叫Higher-Order Components,簡稱HOC
官方的定義:高階組件是參數(shù)為組件,返回值為新組件的函數(shù)
首先,高階組件 本身不是一個組件,而是一個函數(shù)
其次,這個函數(shù)的參數(shù)是一個組件, 返回值也是一個組件
高階組件的調(diào)用類似于這樣:
const EnhancedComponent = higherOrderComonent(WrappedComponent)
Portals 的使用
在某些情況下,我們希望渲染的內(nèi)容獨立于父組件,甚至是獨立于當(dāng)前掛載到的DOM元素中,(默認(rèn)都是掛載到id為root的DOM元素上的)
Portal 提供了一種將子節(jié)點渲染到存在父組件以外的DOM節(jié)點的優(yōu)秀的案例
--第一個參數(shù)(child) 是任何可渲染的React子元素,例如一個元素,字符串或fragment;
-- 第二個參數(shù)是(container) 是一個DOM元素
ReactDOM.createPortal(child,container)
通常來講,當(dāng)你從組建的render方法返回一個元素時,該方法被掛載到DOM節(jié)點中離其最近的父節(jié)點:
然而,有時候?qū)⒆釉夭迦氲紻OM節(jié)點中的不同位置也是有好處的
render() {
// React 掛載一個新的div, 并且把子元素渲染其中
return(
<div>
{this.props.children}
</div>
)
}
render() {
// React 并沒有創(chuàng)建一個新的div 它只是把子元素渲染到'DOMNode'中
return ReactDOM.createPortal(
this.props.children,
domNode
)
}
Modal 組建案例
- 比如說,我們準(zhǔn)備開發(fā)一個Modal組件, 他可以將他的子組件渲染到品目的中間位置:
fragment的使用
// 1.fragment的使用 導(dǎo)入Fragment,類似于小程序中的 block 標(biāo)簽
import React, { PureComponent, Fragment } from 'react';
export default class App extends PureComponent {
constructor(props) {
super(props)
this.state = {
count: 0,
friends:[
{name:"huzhenchu",age:18,sex:"男"},
{name:"huzhenchu",age:18,sex:"男"},
{name:"huzhenchu",age:18,sex:"男"},
]
}
}
render() {
return (
// 2.Fragment標(biāo)簽,類似于小程序中的 block 標(biāo)簽
// <Fragment>
// <h2>當(dāng)前計數(shù):{ this.state.count }</h2>
// <button onClick={e=> this.addNum()}> + 1 </button>
// </Fragment>
// 或者還可以這樣寫de
<>
<h2>當(dāng)前計數(shù):{this.state.count}</h2>
<button onClick={e => this.addNum()}> + 1 </button>
<div>
{
this.state.friends.map(item=> {
return (
// <Fragment>
// <div>{item.name}</div>
// <div>{item.age}</div>
// </Fragment>
// 或者這樣寫 但是你要寫key的話,這種段語法 是不可以添加任何屬性的
// 這種情況你必須要寫 <Fragment key={index}></Fragment>
<>
<div>{item.name}</div>
<div>{item.age}</div>
</>
)
})
}
</div>
</>
);
}
addNum() {
this.setState({
count: this.state.count + 1
})
}
}
StrictMode:React
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
)
StrictMode 是一個用來突出顯示應(yīng)用程序中潛在問題的工具
-- 與Fragment一樣,StrictMode不會渲染任何可見的UI;
--它為其后代元素觸發(fā)額外的檢查和警告
--嚴(yán)格模式檢查僅在開發(fā)模式下運行;他們不會影響生產(chǎn)構(gòu)建可以為應(yīng)用程序的任何部分啟用嚴(yán)格模式
--不會對Header和Footer組件運行嚴(yán)格模式檢查
--但是,ComponentOne 和 ComponentTwo以及他們的所有后代元素都將進(jìn)行檢查
<Header />
<React.StrictMode>
<div>
<ComponentOne />
<ComponentTwo />
</div>
</React.StrictMode>
<Footer />嚴(yán)格模式檢查什么?
--1. 識別不安全的生命周期
--2. 使用過時的ref API
--3. 檢查意外的副作用
--4. 使用廢棄的findDOMMNode
組件化開發(fā)的CSS
- 局部CSS的樣式
- 動態(tài)的CSS的書寫
- 支持所有的CSS 特性
- 避免樣式的全局污染
React 中的四種CSS樣式的編寫
1.1. 內(nèi)聯(lián)樣式的寫法
-- style 接收一個采用小駝峰命名的屬性的JavaScript對象,而不是CSS字符串
-- 并且可以引用state中的狀態(tài)來設(shè)置相關(guān)的樣式
1.2. 內(nèi)聯(lián)樣式的優(yōu)點
-- 1. 內(nèi)聯(lián)樣式,樣式之間不會有沖突
-- 2. 可以動態(tài)獲取當(dāng)前state中的狀態(tài)
1.3. 內(nèi)聯(lián)樣式的缺點:
--寫法上都需要使用駝峰標(biāo)識
--某些樣式?jīng)]有提示
--大量的樣式,代碼混亂
--某些樣式無法編寫(比如偽類/偽元素)
- 使用普通的css來寫,導(dǎo)入到各個JS 文件中
-- 缺點: 會覆蓋和層疊其他文件中的css樣式
-
CSS modules
(1) CSS modules 并不是React特有的解決方案,而是所有使用了類似于webpack配置的環(huán)境下都可以使用的.
--但是,如果在其他項目中使用,那么我們需要自己來進(jìn)行配置,比如配置webpack.config.js中的modules:true等.(2) React的腳手架已經(jīng)內(nèi)置了css modules 的配置
--.css/.less/.scss等樣式文件都修改成.module.css/.module.less/.module.scss等(3) 這樣寫的話 就可以完美的解決層疊問題和樣式覆蓋問題的
(4) 優(yōu)點 確實解決了局部的作用域問題,
(5) 缺點
--引用的類名,不能使用連接符比如(.home-title),在JavaScript中是不識別的
--所有的className都必須使用(style.classname)的形式來編寫
--不方便動態(tài)來修改某些樣式,依然需要使用內(nèi)聯(lián)樣式的方式
CSS in JS
(1) "CSS-in-JS" 是一種模式,其中CSS由JavaScript生成而不是在外部定義的
(2) 注意此功能并不是React的一部分,而是由第三方庫提供,React對樣式如何定義并沒有明確態(tài)度
(3) 事實上CSS-in-JS的模式就是一種將樣式(CSS) 也寫入到JavaScript中的方式,并且可以方便的使用JavaScript的狀態(tài),所以有被人稱之為 All in JS-
認(rèn)識styled-components
(1) CSS-in-JS通過JavaScript來為CSS賦予一些能力,包括類似于CSS預(yù)處理器一樣的樣式嵌套,函數(shù)定義,邏輯復(fù)用,動態(tài)修改狀態(tài)..
(2) 依然CSS預(yù)處理器也是具備某些能力,但是獲取動態(tài)狀態(tài)依然是一個不好處理的點;
(3) 目前CSS-in-JS可以說是React最受歡迎的一種解決方案(4) 目前比較流行的CSS-in-JS 的庫有哪些呢?
--styled-components
--emotion
--glamorous
(5) 目前可以說 styled-components依然是社區(qū)最流行的CSS-in-JS(6) 安裝styled-components
yarn add styled-components
React 中 編寫className的寫法 React中添加Class :
- React在JSX給了我們開發(fā)者足夠多的靈活性, 你可以像JavaScript代碼一樣,通過一些邏輯來決定時候添加某些class:
{/* 這些都是原生的React添加class 的方法 */}
<h2 className={"foo bar active title"}>我是標(biāo)題一</h2>
{/* 這種寫 如果title的后面或者 active的前面不加一個空格的話就會連到一起,這種寫不是很方便 */}
<h2 className={"title " + (isActive ? "active" : "")}> 我是標(biāo)題二 </h2>
{/* 這樣的話比較清楚,為什么呢? 這個地方是可以寫一個數(shù)組的 */}
<h2 className={["title", (isActive ? "active" : "")].join(" ")}>我是標(biāo)題三</h2>
這個時候我們就可以借助第三方庫:classnames
-- 很明顯,這個是一個用于動態(tài)添加className的一個庫庫的安裝:
yarn add classname
classNames 庫的具體用法:
import React, { PureComponent } from 'react';
/**
*
*
*
* 1. React在JSX給了我們開發(fā)者足夠多的靈活性, 你可以像JavaScript代碼一樣,通過一些邏輯來決定時候添加某些class:
* 這些都是原生的React添加class 的方法
* <h2 className={"foo bar active title"}>我是標(biāo)題一</h2>
* 這種寫 如果title的后面或者 active的前面不加一個空格的話就會連到一起,這種寫不是很方便
* <h2 className={"title " + (isActive ? "active" : "")}> 我是標(biāo)題二 </h2>
*
* 這樣的話比較清楚,為什么呢? 這個地方是可以寫一個數(shù)組的
* <h2 className={["title", (isActive ? "active" : "")].join(" ")}>我是標(biāo)題三</h2>
* 2. 這個時候我們就可以借助第三方庫:classnames
* -- 很明顯,這個是一個用于動態(tài)添加className的一個庫
* 3. 庫的安裝命令:
* yarn add classname
*
* **/
// 1. 現(xiàn)下載安裝 className庫 yarn add classname
// 2. 導(dǎo)入
import classNames from "classname"
class App extends PureComponent {
constructor(props) {
super(props);
this.state = {
isActive: true,
isBar: false
}
}
render() {
const { isActive, isBar } = this.state;
const errorClass = "error";
const warnClass = null; // 這個不會被加進(jìn)去
const undiClass = undefined; // 這個不會被加進(jìn)去
const zreo = 0; // 這個不會被加進(jìn)去
const tenNum = 10 // 數(shù)字為真的時候會被加進(jìn)去
return (
<div>
{/* 這些都是原生的React添加class 的方法 */}
<h2 className={"foo bar active title"}>我是標(biāo)題一</h2>
{/* 這種寫 如果title的后面或者 active的前面不加一個空格的話就會連到一起,這種寫不是很方便 */}
<h2 className={"title " + (isActive ? "active" : "")}> 我是標(biāo)題二 </h2>
{/* 這樣的話比較清楚,為什么呢? 這個地方是可以寫一個數(shù)組的 */}
<h2 className={["title", (isActive ? "active" : "")].join(" ")}>我是標(biāo)題三</h2>
<hr />
{/* classnames庫添加class 語法使用如下: */}
<h2 className={"foo bar active title"}>我是標(biāo)題一</h2>
<h2 className={classNames("foo", "bar", "active")}>我是標(biāo)題四</h2>
{/* 如果有些屬性是必須要加的話,你就寫在對象的外面,如下:"title" */}
<h2 className={classNames({ "active": isActive, "bar": isBar }, "title")}>我是標(biāo)題五</h2>
{/* 可以跟變量 */}
<h2 className={classNames("foo", errorClass, warnClass, undiClass, zreo, tenNum, { "active": isActive })}>我是標(biāo)題六</h2>
<h2 className={classNames(["active", "title"])}>我是標(biāo)題七</h2>
<h2 className={classNames(["active", "title", { "active": isActive }])}>我是標(biāo)題四</h2>
</div>
);
}
}
export default App;
AntDesign 的使用
AntDesign 安裝 使用 npm 或者 yarn 安裝
npm install antd -save
或者
yarn add antd'Antd 是否有多余的代碼邏輯添加進(jìn)來呢?
不會的
認(rèn)識craco
- 想要修改create-react-app 的默認(rèn)配置可以使用:
yarn run eject 可以把信息全部展示出來,但是這種不太推薦
- 推進(jìn)的修改 craco
- 安裝craco 這個包
-- yarn add @craco/craco - 還要安裝一個命令:
-- yarn add craco-less - 然后安裝 craco-less 并修改 craco.config.js 文件如下。
- 安裝craco 這個包
const CracoLessPlugin = require('craco-less');
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
lessOptions: {
modifyVars: { '@primary-color': '#1DA57A' },
javascriptEnabled: true,
},
},
},
},
],
};
文件配置取別名:
const CracoLessPlugin = require('craco-less');
// 導(dǎo)入當(dāng)前路徑path 模塊, 這個就是node_modules里面自帶的一個模塊路徑
const path = require('path')
// __dirname:當(dāng)前頁面的路徑,dir 是你要傳入的一個路徑 然后 path.resolve 做一個拼接
const resolve = dir => path.resolve(__dirname, dir)
// 修改主題顏色
module.exports = {
plugins: [
{
plugin: CracoLessPlugin,
options: {
lessLoaderOptions: {
// 修改主題顏色
lessOptions: {
modifyVars: { '@primary-color': '#f00' },
javascriptEnabled: true,
},
},
},
},
],
//webpack 別名的修改配置
webpack: {
alias: {
"@": resolve("src"), // @:這個意思就是代表當(dāng)前路徑所在的 src的這個路徑
"component":resolve("src/component"), // component就是當(dāng)前所在的路徑 所在的src下面的component,如果以后寫component就是找src/component路徑了
}
}
};
axios 的網(wǎng)絡(luò)請求
- Axios 的基本使用命令:
yarn add axios
React-transition-group介紹
React社區(qū)為我們提供了react-transition-group用來過渡完成動畫
-
這個是需要安裝一個插件的,安裝命令:
npm 安裝
npm install react-transition-group --save
yarn
yarn add react-transition-group
-
react-transition-group 主要包含四個組件:
-- Transition
該組件是一個和平臺無關(guān)的組件(不一定要結(jié)合CSS);
一般是結(jié)合CSS來完成樣式, 比較常用的是CSSTransition-- CSSTransition
在前端開發(fā)中,通常使用的是CSSTransition來完成過渡動畫效果--SwitchTransition
兩個組件顯示和影藏切換時,使用該組件--TransitionGroup
將多個動畫組件包裹在其中, 一般用于列表中元素的動畫
Redux的核心理念-reducer
reducer 是一個純函數(shù)
reducer做的事情就是將傳入的state和action結(jié)合起來生成一個新的state
Redux的三大原則
--整個應(yīng)用程序的state被儲存在一個object tree中,并且這個object tree只儲存在一個store中;
--Redux并沒有強制讓我們不能創(chuàng)建多個Store,但是那樣做并不利于數(shù)據(jù)的維護(hù)
--單一的數(shù)據(jù)源可以讓整個應(yīng)用程序的state變得方便維護(hù),追蹤,修改State是只讀的
--唯一修改state的方法一定是觸發(fā)action,不要試圖在其他地方通過任何的方式來修改State
--這樣就確保了View或網(wǎng)絡(luò)請求都不能直接修改state,他們只能通過action來描述自己想要如何修改state
--這樣可以保證所有的修改都被集中化處理,并且按照嚴(yán)格的順序執(zhí)行,所以不需要擔(dān)心race condition 的問題使用純函數(shù)來執(zhí)行修改
-- 通過reducer將舊state和actions聯(lián)系在一起,并且返回一個新的State
--隨著應(yīng)用程序的復(fù)雜度增加, 我們可以將reduce拆分成多個小的reducers,分別操作不同state tree的一部分
--但是所有的reducer都應(yīng)該是純函數(shù), 不能產(chǎn)生任何的副作用;redux三個核心的東西就是: reducer store actios
--把所有的state儲存在store中 想要修改的話 必須要通過 actions 然后通過reducer來進(jìn)行視圖的更改, 但是所有的reducer都應(yīng)該是純函數(shù),所有的數(shù)據(jù)應(yīng)該只存在一個store中
React-router 路由
- 安裝react-router會自動幫助我們安裝react-router的依賴 命令如下:
yarn add react-router-dom
- react-router最主要的API是給我們提供的一些組件
(1) BrowserRouter或HashRouter
--Router中包含了對路徑的改變的監(jiān)聽,并且將會相應(yīng)的路徑傳遞給子組件
--BrowserRouter使用history模式
--HashRouter使用hash模式
(2) Link和NavLink
--通常路徑的跳轉(zhuǎn)是使用Link組件,最終會被渲染成a 元素
--NavLink是在Link基礎(chǔ)之上增加了一些樣式屬性
--to屬性:LInk最重要的屬性,用于設(shè)置跳轉(zhuǎn)到的路徑
(3) Route:
--Route用于路徑的匹配
--path屬性:用于設(shè)置匹配到的路徑
--component屬性:設(shè)置匹配到路徑后,渲染組件
--exact:精準(zhǔn)匹配,只有精準(zhǔn)匹配到完全一致的路徑,才會渲染對應(yīng)的組件