文章目錄
- react學習筆記01
- 入門
- 概述
- React 基礎案例HelloWorld
- 三個API介紹
- JSX
- JSX 解構數組
- 創建react項目(手動)
- 創建React項目(自動) | create-react-app
- 事件處理
- React中的CSS樣式
- 內聯樣式 | 內聯樣式中使用state (不建議使用)
- 外部樣式表 | CSS Module
- React組件
- 函數式組件和類組件
- 生成一組標簽/組件
- props 父組件給子組件傳屬性/方法
- 給組件設置className樣式不生效
- state 維護組件的響應式狀態
- useState(stateInitValue)
- Ref 獲取DOM對象
- 非受控組件與受控組件
- 數據的雙向綁定
- 子組件給父組件傳值 = props傳遞函數 + 子組件調用函數
- vue中v-if與v-show的React寫法
- Portal 將元素渲染到指定位置
- Fragment 組件
- Context 祖先組件向子孫組件傳值
- Effect 副作用
- setState()在函數組件中的執行流程
- React.StrictMode
react學習筆記01
學習視頻 react18 李立超
學習中get到的新用法
-
Date
類的toLocalString
方法,可以更為靈活的處理Date
類。 -
標簽屬性中閉包的使用
舉例:僅在刪除狀態時使用id,不需要單獨傳遞id屬性。const logItemDate = logsData.map(item=> <LogItem onDelLog ={()=> delLog(item.id)}>)
-
移動端適配
rem + vw
可以使用vw
獲取視口寬度,將font-size
設置單位為vw
,然后結合rem
做適配。
1vw = 視口寬度的1%
->100vw = 視口的寬度
一般設置
html的font-size值 = 屏幕寬度/設計稿寬度
,但移動端比如375px
計算出的font-size
值小于12px
會造成一些錯誤和奇怪的問題,因此把比例擴大100倍
。為了使比例不變,相應的設計圖元素使用時
設計圖元素大小/100 rem
根html的font-size值 = 屏幕寬度/設計稿寬度*100 font-size = 100vw/設計稿寬度*100
入門
概述
AJAX+DOM
可以實現網頁的局部刷新,但是新數據不能直接在網頁中顯示,需要通過DOM將數據轉換為網頁中的節點。
react
幫助我們根據不同的數據來快速構建用戶項目,同時在構建過程中確保其流暢度。
react
特點
1.使用虛擬DOM而不是真正的DOM
2.聲明式編碼(聲明式:結果為導向,不關心結果 命令式:一行代碼一個命令)
3.支持服務器端渲染
React 基礎案例HelloWorld
入門案例采用外部引入腳本使用(正常開發使用包管理器)
react.development.js
react
是react
核心庫,只要使用react
就必須要引入。下載地址react-dom.development.js
react-dom
是react
的dom
包,使用react
開發web
應用時必須引入。下載地址babel.min.js
瀏覽器不能識別JSX
,利用該babel
將JSX
轉換為JS
代碼。下載地址
1.引入腳本
<script src="../script/react.development.js"></script>
<script src="../script/react-dom.development.js"></script>
2.創建一個React
元素
React.createElement(組件名/元素名,元素中的屬性,元素的子元素/內容)
const reactDiv = React.createElement('div',{},'我是react創建的div');
3.獲取根元素對應的React
元素
ReactDOM.createRoot(Dom元素);
// html
<div id="root"></div>
// js
const root = ReactDOM.createRoot(document.getElementById('root'));
4.將reactDiv
渲染到React
根元素中
root.render(reactDiv)
三個API介紹
-
React.createElement(type,[props],[...children])
用來創建React
元素(并不是ReactDom
,所以這里使用React
調用)-
class
屬性需要使用className
屬性代替。 -
type
如果是標簽名(元素)需要全小寫,首寫母大寫會被認為是組件 -
在設置屬性時,事件名應遵守駝峰命名法,事件值需要是一個函數,不能是
console.log(xx)
這種表達式。如果直接寫一個函數調用語句,則在綁定事件時就會被調用(之后事件不會被觸發) -
React元素是一次性的,一旦創建就無法修改,只能使用新創建的元素進行替代
-
-
ReactDOM.createRoot(container[,options]);
用來創建React的根容器,根容器用來放置React
元素- 將參數的
DOM
元素轉換為React
根元素
- 將參數的
-
ReactDOM實例.render(ReactElement)
將React
元素渲染到根元素中DOM
根元素中所有的內容都會被刪除(不會修改DOM
根元素本身),被React
元素轉換而成的DOM
元素替換- 重復調用
render()
,React會將兩次虛擬DOM
進行對比,確保只修改發生變化的元素,對DOM做最少修改。首次調用時,容器節點里的所有DOM都會被替換,后續的調用則會使用React
的DOM
差分算法(diff
)進行更新
JSX
上述方法中React.createElement('button', {}, '我是按鈕')
還是命令式編碼方法,告訴react
用createElement
去創建一個button
按鈕,該按鈕沒有屬性,內容為我是按鈕。
聲明式編程結果導向,告訴結果,不關系過程怎么樣。
const button = <button>我是按鈕</button>; // 告訴react我需要一個button按鈕元素,不關心react如何創建
在React
中可以通過JSX
(JavaScript Syntax Extension
)來創建React
元素,JSX
讓我們以類似于HTML
的形式去使用 JS
。JSX
是React
中聲明式編程的體現方式。
JSX
需要被翻譯為JS
代碼,才能被React
執行。 要在React中使用JSX
,必須引入babel
來完成“翻譯”工作。
-
JSX
就是React.createElement()
的語法糖,最終都會轉換為以調用React.createElement()
創建元素的代碼。 -
JSX
在執行之前都會被babel
轉換為JS
代碼<!-- 引入babel --> <script src="script/babel.min.js"></script> <!--設置js代碼被babel處理--> <script type="text/babel">const div = <div>我是一個div<button>我是按鈕</button></div>;const root = ReactDOM.createRoot(document.getElementById('root'));root.render(div); </script>
-
JSX
不是字符串,不需要加引號
const div = <div>我是一個div</div> // 正確寫法
JSX
中html
標簽應該小寫開頭,React
組件應該大寫開頭
<div> // 小寫html標簽
<Div> // 大寫組件
-
JSX
有且只有一個根標簽 -
JSX
的標簽必須正常結束(自結束標簽必須寫/
)
const input = <input type="text" / >
- 在
JSX
中使用{}
嵌入表達式(有值的語句就是表達式)
const name = "ranran"
const div = <div>{name}</div> // 才會顯示ranran,沒有括號會把name識別為字符串
-
如果表達式值為空值、布爾值、undefined,將不會顯示
-
在
JSX
屬性可以直接在標簽中設置- 事件綁定需要是一個函數,而不能直接是函數調用(綁定時就會被觸發,不會延遲觸發)
className
代替class
style
必須使用對象設置,屬性名必須用駝峰命名法
const div = <div onClick="()=>{console.log('ranran')}" style={{backgroundColor: "yellowgreen", border: '10px red solid'}}
></div> // 外面的大括號表示style必須使用對象設置,里面的對象表示給他設置的值是一個對象(有多個樣式)
-
在語句中可以操作
JSX
const name = 'ranran'; const lang = 'cn';let div; if(lang === 'en'){div = <div>hello {name}</div>; }else if(lang === 'cn'){div = <div>你好 {name}</div>; } const root = ReactDOM.createRoot(document.getElementById('root')) root.render(div)
JSX 解構數組
JSX在解構{}
的內容時,如果內容是數組則會自動將其展開。
//頁面:孫悟空豬八戒沙和尚
const data = ['孫悟空', '豬八戒', '沙和尚'];
const div = <div>{data}</div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(div)/*
· 孫悟空
· 豬八戒
· 沙和尚
*/
const data = ['孫悟空', '豬八戒', '沙和尚'];
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)//頁面:孫悟空豬八戒沙和尚
const data = ['孫悟空', '豬八戒', '沙和尚'];
const div = <div>{data}</div>
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(div)/*
· 孫悟空
· 豬八戒
· 沙和尚
*/
const data = ['孫悟空', '豬八戒', '沙和尚'];
const list = <ul>{data.map(item => <li>{item}</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)
React通過虛擬DOM
將React
元素和原生DOM
元素進行映射
當我們調用root.render時。頁面就會發生重新渲染
React通過diff
算法將新的虛擬DOM和舊的比較,找到發生變化的元素,并且只對變化的元素進行修改。
數組中(當前數組)每一個元素都需要設置一個唯一key
值
重新渲染頁面時,React
有key
值會比較key
值相同的元素,沒key
值會按照順序進行比較。
- 開發中一般會采用數據的
id
作為key
- 盡量不使用元素的
index
作為key
索引會跟著元素順序的改變而改變,所以使用索引做key
跟沒有key
是一樣的。 唯一的不同就是,控制臺的警告沒了。 當元素的順序不會發生變化時,用索引做key
也沒有什么問題。
const data = ['孫悟空', '豬八戒', '沙和尚'];
const list = <ul>{data.map(item => <li key={ item }>{ item }</li>)}</ul>;
// const list = <ul>{data.map((item,index) => <li key={ index }>{ item }</li>)}</ul>;
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(list)
?
創建react項目(手動)
React
官方為了方便開發,提供react-scripts
包(①打包②測試服務器-根據代碼變化自動刷新避免改一點就重新打包),包中提供了項目開發中的大部分依賴。
由于提供了配置好的工具,我們一些操作就要符合約定。
使用包管理器管理項目,沒有辦法直接放在網頁中運行。需要經過webpack打包,才能在瀏覽器中正常執行。
- 創建
React
根目錄- public(可以web直接訪問的文件,不用打包就可以瀏覽器訪問的靜態資源)- index.html (入口文件,必須有,首頁模板打包時以此為模板生成最終的index/html | 添加標簽 <div id="root"></div>)- src(源碼,JS源代碼)- index.js(必須,webpack打包文件的入口,該文件會被自動引入public/index.html中)
pnpm init
初始化項目,生成package.json
文件(大部分時候這一步可以省略)pnpm install react react-dom react-scripts
安裝項目依賴- 編寫代碼
src/index.js
// 引入ReactDOM
import ReactDOM from 'react-dom/client';// 創建一個JSX
const APP = <div><h1>這是一個react項目</h1></div>// 獲取一個根元素
const root = ReactDOM.createRoot(document.getElementById('root'));
// 將APP渲染進根容器
root.render(APP);
- 運行項目
pnpm react-scripts build
打包項目,一般開發完成之后需要上線時使用該命令進行打包。
初次需要輸入y確認。打包時需要默認配置,會詢問是否添加默認配置。
正常情況,右鍵打開會報錯。因為打包好的文件需要部署在服務器上運行,而不是直接使用瀏覽器打開。每次打包后路徑都是這樣需要手動修改。
-
pnpm react-scripts start
開發中使用的命令
通過webpack
啟動內部的測試服務器,可以實時對更新代碼進行編譯。這個命令太長,可以在package.json
的scripts
選項中配置命令,下次可以使用命令pnpm start
。"scripts": {"start": "react-scripts start" }
react 一定需要兩個文件
public/index.html
:入口文件,首頁模板打包時以此為模板生成最終的index/html
- 提供dom root
根節點src/index.js
:webpack
打包文件的入口,該文件會被自動引入public/index.html
中 - 將root轉化為react根節點元素后,將react元素掛載到react根節點中
創建React項目(自動) | create-react-app
命令:npx create-react-app 項目名
除了public/index.html
和src/index.js
必須保留外,其他的東西都是可以刪除的。
/*reate-react-app 創建index.js其中<React.StrictMode>使用嚴格模式渲染React元素 - 可以不使用
*/
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<React.StrictMode>aaa</React.StrictMode>
);
事件處理
react
元素的事件處理和 DOM
元素的很相似,但是有一點語法上的不同:
-
React
事件的命名采用小駝峰式(camelCase
),而不是純小寫。 -
使用
JSX
語法時需要傳入一個函數作為事件處理函數。事件綁定需要是一個函數,而不能直接是函數調用(綁定時就會被觸發,不會延遲觸發,等于將函數的返回值給了該事件)// 傳統 HTML <button onclick="activateLasers()">Activate Lasers </button> // React <button onClick={activateLasers}> Activate Lasers </button>
-
React
事件通過會傳遞事件對象event
,但其不同于原生的事件對象,是React
包裝后的事件對象,該對象已經處理了跨瀏覽器的兼容性問題。React
中事件回調函數不能通過返回false
阻止默認行為,必須顯式地使用event
事件對象的preventDefault
方法// 傳統 HTML<form οnsubmit="console.log('You clicked submit.'); return false"><button type="submit">Submit</button> </form>// React function Form() {function handleSubmit(e) {e.preventDefault(); console.log('You clicked submit.');}return (<form onSubmit={handleSubmit}><button type="submit">Submit</button></form>); }
React中的CSS樣式
內聯樣式 | 內聯樣式中使用state (不建議使用)
style
必須使用對象設置,屬性名必須用駝峰命名法
const StyleDemo = () => {return (<div style={{color:'red', backgroundColor:'#bfa', fontSize:20, borderRadius:12}}>我是Div</div>);
};export default StyleDemo;
當樣式過多,JSX
會比較混亂,可以使用變量去保存對象
import React from 'react';const StyleDemo = () => {const divStyle = {color: 'red', backgroundColor: '#bfa', fontSize: 20, borderRadius: 12}return (<div style={divStyle}>我是Div</div>);
};export default StyleDemo;
內聯樣式中使用state
當樣式是動態時,可以在樣式中使用state變量。
import React, {useState} from 'react';const StyleDemo = () => {const [showBorder, setShowBorder] = useState(false);const divStyle = {color: 'red',backgroundColor: '#bfa',fontSize: 20,borderRadius: 12,border: showBorder?'2px red solid':'none'};const toggleBorderHandler = ()=> {setShowBorder(prevState => !prevState);};return (<div style={divStyle}>我是Div<button onClick={toggleBorderHandler}>切換邊框</button></div>);
};export default StyleDemo;
外部樣式表 | CSS Module
外部樣式是指將樣式編寫到外部的css
文件中,直接通過import
引用。
直接import引入的樣式都是全局樣式,其他組件也看得見這個樣式。如果不同的樣式表中出現了相同的類名,會出現相互覆蓋情況。
import './index.css'
CSS Module
使用CSS Module
后,網頁中元素的類名會自動計算生成并確保唯一。
如果引用同一個模塊,計算出來的類名是相同的。
CSS Module
在React
中已經默認支持(前提是使用了react-script
)
- 文件樣式的文件名為
xxx.module.css
- 在組件中引入樣式的格式為
import xxx from './xxx.module.css'
- 設置類名時通過
xxx.yyy
的形式來設置
/*
StyleDemo.module.css
*/
.myDiv{color: red;background-color: #bfa;font-size: 20px;border-radius: 12px;
}/*
StyleDemo.js
*/
import Styles from './StyleDemo.module.css';const StyleDemo = () => {return (<div className={Styles.myDiv}>我是Div</div>);
};export default StyleDemo;
React組件
組件需要遵守的規則
- 組件名首字母必須大小(小寫字母開頭的組件會被視為原生DOM標簽)
- 組件中只能有一個根元素
函數式組件和類組件
React
中定義組件有兩種方式
- 基于函數的組件 - 函數式組件(推薦) :函數組件是返回
JSX
普通函數 - 基于類的組件 - 類組件
函數式組件
函數組件是返回JSX
普通函數
//1.創建函數式組件 App.js
const App = () => {return <div>我是App組件!</div>
};
// 2.導出App
export default App;// index.js
// 3.引入App
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById('root'));
// 4.React組件可以直接通過JSX渲染
root.render(<App/>); //root.render(App()); 也可以,只是<App/>內部做了更多的事情。
類組件
1.創建一個ES6 class
,并繼承于React.Component
2.添加一個render
方法,方法的返回值為JSX
import React from "react"
//1.創建類組件 必須要繼承React.Component
class App extends React.Component{constructor(props){ // 參數props接受父組件的傳值this.state = 'xxx' //state的使用}// 2.添加render方法render(){return <div>我是一個類組件{this.props}</div>}
}// index.js
// 3.引入App
import App from "./App";
const root = ReactDOM.createRoot(document.getElementById('root'));
// 4.React組件可以直接通過JSX渲染
root.render(<App/>); //root.render(App()); 也可以,只是<App/>內部做了更多的事情。
props、state、ref
-
類組件的
props
存儲在類的實例對象中,可以通過this.props
訪問。 -
類組件中
state
統一存儲到了實例對象的state屬性中,可以通過this.state
來訪問,通過this.setState()
修改。- 通過
this.setState
修改state
,只修改設置了state
的屬性,并不會修改沒設置的第一層屬性。
- 通過
-
通過
React.createRef()
(函數式為useRef)創建屬性存儲DOM對象,同樣通過對象.current
獲取 -
事件回調函數需要定義為類的方法,建議使用箭頭函數,這樣
this
指向的是react
實例。否則函數里的this會執行設置事件的dom
元素
import React,{ Component } from 'react'class App extends Component{state = {count:0,age:{} } divRef = React.createRef();// 事件回調函數需要定義為類的方法,建議使用箭頭函數,這樣this指向的是react實例。否則函數里的this會執行設置事件的dom元素clickHandler = ()=>{// 寫法1:this.setState({count:this.state.count+1})// 寫法2this.setState(prevCount => {return {count:prevCount+1;}})}retnder(){return <div><h1 ref={ divRef }>this.props<h1><h1 @onClick={this.clickHandler}>this.state.count<h1> </div>}
}
生成一組標簽/組件
在react
中對于根據數組數據產生一組標簽或者一組組件,沒有類似vue
的v-for
指令,一般使用{ data.map(JSX) }
的語法進行生成。
const App= () => {const data = [{title:"1",id:"0"},{title:"2",id:"1"},{title:"3",id:"2"}];return <div>{ data.map(item => <Button key={item.id} titile={item.title}></Button >) }</div> /*寫法1 return <div> { data.map(item => <Button key={item.id} titile={item.title} />) } </div> 寫法2:將對象的每個屬性都傳遞return <div> { data.map(item => <Button {...item}>) </div>}*/
};export default App;
props 父組件給子組件傳屬性/方法
父組件通過使用子組件時,定義自定義參數傳遞屬性/方法。子組件通過參數props
接收(函數式組件的第一個參數)。
react
中的props
類似vue
中的props
,是只讀屬性是無法修改的
props.children
中可以獲取到父組件中,子組件標簽體內部的值。
// 父組件
<Button bgColor='red' color={ color }>我是一個按鈕</Button>//子組件
const Button = (props) => {return <button style={{backgroundColor:props.bgColor, color:props.color}}>{props.children}</button>;
};export default Button;
給組件設置className樣式不生效
原因
className
會被認為是一個屬性傳遞給子組件,需要在子組件的根元素使用className={props.className}
接收。
state 維護組件的響應式狀態
在React
中,當組件渲染完畢后,再修改組件中的變量,不會使組件重新渲染。state
相當于一個變量,只不過在React
中進行了注冊。React
會監控整個變量的變化,當state
發生變化時,會自動觸發組件的重新渲染。
頁面的渲染靠的是
render
函數
state概述
state
與props
類似,都是一種存儲屬性的方式。
- state只屬于當前組件(組件的私有屬性),其他組件無法使用。
- state的值是對象,當其內容發生變化相關組件會一起刷新
useState(stateInitValue)
通過鉤子函數useState(stateInitValue)
創建state
,React
中鉤子函數只能用于函數組件或自定義鉤子。
- 參數是整個
state
變量的初始值 - 函數返回一個數組
[stateVariable,setStateFunction]
,第一個元素是state
變量的初始值(只用于顯示),第二個元素是修改該變量的函數(函數的參數為新值)。調用修改函數修改state
變量的值(state值發生變化)會觸發組件的重新渲染,直接修改state變量不會觸發組件的重新渲染。
import { useState } from 'React'const [stateVariable,setStateFunction] = useState(1);
注意點
- 當
state
值是一個對象時,setState()
修改時,使用新的對象去替換已有對象。
const [user, setUser] = useState({name:"ranran",age:18})
user.name = "xxx";
serUser(user); // user是對象,對象的地址沒有發生變化,所以不會引起組件重新渲染/*
解決方案:將其拷貝給另一個新對象,修改新對象的屬性
*/
setUser({...user,name:"xxx"}) // 后面的name會覆蓋前面的name
-
通過
setState()
去修改一個state
時,并不表示修改當前的state
,修改的是組件下一次渲染的state
-
setState()
會觸發組件的異步渲染(并不是馬上調用就渲染,放入事件循環隊列中等待執行),所以當調用setState()
需要使用state值時,可能出現計算錯誤。因為
setState()
修改的是下一次渲染的state
,如果下一次渲染還沒進行前又調用了setState()
,此時state
還是舊值,所以就會出現計算錯誤。解決辦法 : 通過傳遞回調函數的形式修改
state
回調函數的返回值會成為新的state值,回調函數執行時
React
會將最新的state
值作為參數傳遞。setCount(state => state+1); // 傳遞參數,React會保證參數的state是最新值
如果setState()
中需要用到舊值,參數都采用函數的形式。
Ref 獲取DOM對象
Ref
是reference
的簡寫,用來獲取真實DOM
的引用。
- 使用
useRef()
鉤子函數獲取DOM
對象- 1.通過
useRef()
鉤子函數返回一個普通JS對象,React
會自動將DOM
對象傳遞到該對象的current
屬性中。 - 2.被引用的DOM元素上添加
ref
屬性,值為上述的對象。
根據描述,直接創建一個有current
屬性的普通JS
對象可以實現相同的效果。
- 1.通過
兩種方法的不同點
- 自定義對象方法,組件每次重新渲染,都會創建一個新對象
- 使用
useRef()
函數返回的對象的聲明周期和組件的聲明周期一致,所以每次重新渲染,該ref
對象都是原來的。
import {useRef} from 'react';const MyComponent = () => {const divRef = useRef();/*const divRef = {current:null}*/const clickHandler = () => {console.log(divRef);};return (<div ref={divRef} onClick={clickHandler}>一個div</div> );
};export default MyComponent;
非受控組件與受控組件
非受控組件:表單中的數據來源于用戶填寫的組件,表單元素的值不會更新state,輸入數據都是現用現取的。
受控組件:使 React
的 state
成為唯一數據源,由state
控制表單。
數據的雙向綁定
將表單的value
綁定為state
數據,表單的onChange
事件觸發時,通過事件對象event
獲取到新值,然后使用setState
修改state
的值為新值。
import { useState } from 'react';
import './index.css';const Demo = () => {// 如果有多個表單,可以將表單數據設置為一個對象const [inputValue, setInputValue] = useState('');return (<><inputtype="text"className="inputDemo"value={inputValue}onChange={e => {setInputValue(e.target.value);}}/></>);
};export default Demo;
子組件給父組件傳值 = props傳遞函數 + 子組件調用函數
- 在父組件中,使用
props
給子組件傳遞一個自定義事件 - 在子組件中將需要傳遞的數據作為函數參數,調用函數
// 父組件
<LogsItem onSavaLog={ savaLogHandler }>// 子組件
const LogsItem = (props) => {props.savaLogHandler("需要傳遞的數據");
}
關于傳遞
setState
函數給子組件的一些說法:盡量不要這樣做,state
在哪里,setState
盡量就在哪里。
vue中v-if與v-show的React寫法
v-if
-v-else
配對出現 可以使用條件判斷v-show
/僅有v-if
可以使用&&
// v-if/v-else 可以使用條件判斷
控制變量 ? v-if顯示的 : v-else顯示的// v-show/僅有v-if 可以使用&&
控制變量 && v-show顯示的
如果顯示出來的組件內部需要修改外部的控制變量,
react
中一般的做法時將函數作為參數傳遞。因為控制變量在外部,內部只需要調用該函數,外部修改控制變量的值。
Portal 將元素渲染到指定位置
在React
中,父組件引入子組件后,子組件會直接在父組件內部渲染。換句話說,React
元素中的子組件,在DOM
中,也會是其父組件對應DOM
的后代元素。
問題描述
每個組件都是相同的構成(想象成一個列表),組件內部包含一個子組件,該子組件的作用是生成一個遮罩覆蓋全局。
組件1開啟相對定位,遮罩開啟固定定位(不一定是和這個例子相同的定位方式,這里舉例)
由于組件1組件2組件3的 z-index:1
,后面的組件會覆蓋前面的。所以組件1中的遮罩出現時,覆蓋不了組件2組件3,即使遮罩的z-index:999
(理解為在組件1內部元素的層級中占比很高,但不影響組件1的層級),但組件1和其他兄弟組件層級相同(父元素組件1都被覆蓋了子元素肯定被一起覆蓋)。
結構問題:遮罩需要遮住視圖不應該作為組件123的子組件,如果必須這樣寫,解決辦法是使用Portal
將組件渲染到指定位置
ReactDOM.createPortal(需要渲染的元素,傳送到的指定位置)
:渲染元素時將元素渲染到網頁中的指定位置
1.在index.html
中添加一個新的元素
<div id="root"></div>
<!--這個容器用來專門渲染遮罩層-->
<div id="backdrop"></div>
2.在組件中通過ReactDOM.createPortal()將元素渲染到新建的元素中
const backdropDOM = document.getElementById('backdrop');// 在其他組件內部正常使用Backdrop組件,但是該組件渲染時會被傳送到專門渲染遮罩層的容器中渲染,會脫離原來的結構
const Backdrop = () => {return ReactDOM.createPortal(<div>{props.children}</div>,backdropDOM);
};
Fragment 組件
在React
中,JSX
必須有且只有一個根元素,這導致在某些情況需要添加一個額外的父元素(并沒有實際意義)
React
提供了Fragment
組件,Fragment
可以讓你聚合一個子元素列表,并且不在DOM中增加額外節點<></>
是Fragment
的語法糖,<></>
語法不能接受鍵值或屬性,但Fragment
可以傳遞key
屬性
import React from 'react';const MyComponent = () => {return (<React.Fragment><div>我是組件1</div><div>我是組件2</div><div>我是組件3</div></React.Fragment>/*<><div>我是組件1</div><div>我是組件2</div><div>我是組件3</div></>*/);
};export default MyComponent;
Context 祖先組件向子孫組件傳值
Context
相當于一個公共的存儲空間
創建content
// defaultValue存儲的值
export const MyContext = React.createContext({name:xxx,age:xxx,
});
訪問到Context中的數據
-
方式1:通過
Consumer
標簽來訪問到Context
中的數據(不常用)該組件內部必須使用函數,解析時會調用該函數,將創建的
defaultValue
作為該函數的參數傳遞。import React from 'react'; import { MyContext } from '../store/test-context';const MyContext = () => {return (<MyContext.Consumer>{(ctx)=>{ // 上述案例中的defaultValuereturn (<ul><li>{ctx.name}</li><li>{ctx.age}</li></ul>);}}</MyContext.Consumer>); }; export default MyComponent;
-
方式2:使用鉤子函數
useContext(context參數)
獲取到context
,該鉤子函數會返回Context
中的數據import React, {useContext} from 'react'; import { MyContext } from '../store/test-context';const MyComponent = () => {const ctx = useContext(MyContext);return (<ul><li>{ctx.name}</li><li>{ctx.age}</li></ul>); };export default MyComponent;
? 一般不會將數據直接放在Context
,因為這樣寫是死數據并且與state
響應式數據沒什么關系,不會觸發組件的重新渲染。所以React
還提供了Provider
組件,用于在數據所在的組件中指定Context
值。
import React from "react";
import MyComponent from "./component/MyComponent";
import { MyContext } from "./store/test-context";// 數據所在的組件
const App = () => {// 指定context的值return <MyContext.Provider value={{name:'豬八戒', age:28}}>/* Provider的子組件 */<MyComponent/> </MyComponent.Provider>;
};export default App;
Provider
設置在外層組件中,通過value
屬性來指定Context
的值。這個Context
值在所有的Provider
子組件中都可以訪問。Context
的搜索流程類似vue
的provide
和 inject
。
Effect 副作用
組件每次重新渲染,組件的函數體就會執行。
有一部分邏輯如果直接寫在函數體中,會影響到組件的渲染,這部分會產生“副作用”的代碼,是不能直接寫在函數體中。
例如,如果直接將修改state的邏輯編寫到了組件之中,每次函數體執行設置基礎值,state變量又引起組件的更新,就會導致組件不斷的循環渲染,直至調用次數過多內存溢出。
setState()在函數組件中的執行流程
setState()
會調用dispatchSetDate()
方法,dispatchSetDate()
方法的主要邏輯
-
判斷組件當前處于什么階段(渲染階段 |非渲染階段 )
-
處于渲染階段:不會檢查
state
值是否相同,在此時直接將setState
設置的值放入渲染隊列等待渲染 -
處于非渲染階段:檢查
setState
設置的值與之前的值是否相同。如果值不同,對組件進行重新渲染;如果值相同,則不對組件進行重新渲染。
-
處于渲染階段案例
const App = () => {const [count,setCount] = uesState(0);// 會觸發Too many re-renders報錯 // 調用的時候處于渲染階段,因為div沒有渲染到頁面上,所以會引發重新渲染,再次調用組件函數。也就是說無限循環,不會退出渲染階段。setCount(0); return (<div>{count}</div>)
}
處于非渲染階段案例
第一次點擊按鈕count = 0 -> 1
,組件重新渲染。
第二次點擊按鈕count = 1 -> 1
,組件重新渲染。
第三次點擊按鈕count = 1 -> 1
,組件沒有重新渲染。
這是因為當值相同時,React
在某些情況下(通常發生在值第一次相同時)會繼續執行當前組件的渲染(這里指的時組件函數執行并更新頁面),這次渲染不會產生實際效果(這里應該僅重新執行組件函數并不更新頁面,不觸發刷新沒有什么用??)并且不會觸發子組件的渲染。
const App = () => {const [count,setCount] = uesState(0);const clickHandler = ()=>{setCount(1); }return (<div onClick={ clickHandler }>{count}</div>)
}
React.StrictMode
腳手架自動生成的index.jx
中使用了該組件,該組件表示react
自身開啟嚴格模式,開啟后react
會自動去檢查組件中是否有副作用的代碼(并不是很智能)。
root.render(<React.StrictMode><App/></React.StrictMode>
)
React
的嚴格模式,在開發模式下,會主動重復調用一些函數,以使副作用出現。這些函數會被調用兩次,如果安裝了React Developer Tool
,調試作用的第二次調用會顯示為黑色。
- 類組件的
constructor
,render
, 和shouldComponentUpdate
方法 - 類組件的靜態方法
getDerivedStateFromProps
- 函數組件的函數體
- 參數為函數的
setState
- 參數為函數的
useState
,useMemo
,useReducer