「React 進階」 React 全部 api 解讀 - 基礎實踐大全
很多同學用react開發的時候,真正用到的React的api少之又少,基本停留在Component,React.memo等層面, 實際react源碼中,暴露出來的方法並不少,只是我們平時很少用。但是React暴露出這麼多api並非沒有用,想要玩轉react, 就要明白這些API究竟是幹什麼的,應用場景是什麼,今天就讓我們從react 到 react-dom, 一次性把react生產環境的暴露api複習個遍 (涵蓋 90%+)。
我們把react,API,分爲組件類,工具類,hooks,再加上 react-dom ,一共四大方向,分別加以探討。
爲了能讓屏幕前的你,更明白api, 我是絞盡腦汁, 本文的每一個api基本都會出一個demo演示效果, 彌補一下天書般的react文檔😂😂😂,還有就是我對api基本概念的理解。
老規矩,我們帶着疑問開始今天的閱讀 (自測掌握程度)?
-
1
react暴露的api有哪些,該如何使用? -
2
react提供了哪些自測性能的手段? -
3
ref既然不能用在函數組件中,那麼父組件如何控制函數子組件內的state和方法? -
4
createElement和cloneElement有什麼區別,應用場景是什麼? -
5
react內置的children遍歷方法,和數組方法, 有什麼區別? -
6
react怎麼將子元素渲染到父元素之外的指定容器中? -
...
我相信讀完這篇文章,這些問題全都會迎刃而解?
組件類
組件類,詳細分的話有三種類,第一類說白了就是我平時用於繼承的基類組件Component,PureComponent, 還有就是react提供的內置的組件,比如Fragment,StrictMode, 另一部分就是高階組件forwardRef,memo等。
Component
Component是class組件的根基。類組件一切始於Component。對於React.Component使用,我們沒有什麼好講的。我們這裏重點研究一下react對Component做了些什麼。
react/src/ReactBaseClasses.js
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
這就是Component函數,其中updater對象上保存着更新組件的方法。
我們聲明的類組件是什麼時候以何種形式被實例化的呢?
react-reconciler/src/ReactFiberClassComponent.js
constructClassInstance
function constructClassInstance(
workInProgress,
ctor,
props
){
const instance = new ctor(props, context);
instance.updater = {
isMounted,
enqueueSetState(){
/* setState 觸發這裏面的邏輯 */
},
enqueueReplaceState(){},
enqueueForceUpdate(){
/* forceUpdate 觸發這裏的邏輯 */
}
}
}
對於Component, react 處理邏輯還是很簡單的,實例化我們類組件,然後賦值updater對象,負責組件的更新。然後在組件各個階段,執行類組件的render函數,和對應的生命週期函數就可以了。
PureComponent
PureComponent和 Component用法,差不多一樣,唯一不同的是,純組件PureComponent會淺比較,props和state是否相同,來決定是否重新渲染組件。所以一般用於性能調優,減少 render 次數。
什麼叫做淺比較,我這裏舉個列子:
class Index extends React.PureComponent{
constructor(props){
super(props)
this.state={
data:{
name:'alien',
age:28
}
}
}
handerClick= () =>{
const { data } = this.state
data.age++
this.setState({ data })
}
render(){
const { data } = this.state
return <div class >
<div class >
<div> 你的姓名是: { data.name } </div>
<div> 年齡: { data.age }</div>
<button onClick={ this.handerClick } >age++</button>
</div>
</div>
}
}
PureComponent會比較兩次data對象,都指向同一個data, 沒有發生改變,所以不更新視圖。
解決這個問題很簡單,只需要在handerClick事件中這麼寫:
this.setState({ data:{...data} })
淺拷貝就能根本解決問題。
memo
React.memo和PureComponent作用類似,可以用作性能優化,React.memo 是高階組件,函數組件和類組件都可以使用, 和區別PureComponent是 React.memo只能對props的情況確定是否渲染,而PureComponent是針對props和state。
React.memo 接受兩個參數,第一個參數原始組件本身,第二個參數,可以根據一次更新中props是否相同決定原始組件是否重新渲染。是一個返回布爾值,true 證明組件無須重新渲染,false證明組件需要重新渲染,這個和類組件中的shouldComponentUpdate()正好相反 。
**React.memo: 第二個參數 返回 true 組件不渲染 , 返回 false 組件重新渲染。**shouldComponentUpdate: 返回 true 組件渲染 , 返回 false 組件不渲染。
解析來我們做一個場景,控制組件在僅此一個props數字變量,一定範圍渲染。
例子🌰:
控制 props 中的 number :
-
1 只有
number更改,組件渲染。 -
2 只有
number小於 5 ,組件渲染。
function TextMemo(props){
console.log('子組件渲染')
if(props)
return <div>hello,world</div>
}
const controlIsRender = (pre,next)=>{
if(pre.number === next.number ){ // number 不改變 ,不渲染組件
return true
}else if(pre.number !== next.number && next.number > 5 ) { // number 改變 ,但值大於5 , 不渲染組件
return true
}else { // 否則渲染組件
return false
}
}
const NewTexMemo = memo(TextMemo,controlIsRender)
class Index extends React.Component{
constructor(props){
super(props)
this.state={
number:1,
num:1
}
}
render(){
const { num , number } = this.state
return <div>
<div>
改變num:當前值 { num }
<button onClick={ ()=>this.setState({ num:num + 1 }) } >num++</button>
<button onClick={ ()=>this.setState({ num:num - 1 }) } >num--</button>
</div>
<div>
改變number: 當前值 { number }
<button onClick={ ()=>this.setState({ number:number + 1 }) } > number ++</button>
<button onClick={ ()=>this.setState({ number:number - 1 }) } > number -- </button>
</div>
<NewTexMemo num={ num } number={number} />
</div>
}
}
完美達到了效果,React.memo一定程度上,可以等價於組件外部的shouldComponentUpdate ,用於攔截新老props,確定組件是否更新。
forwardRef
官網對forwardRef的概念和用法很籠統,也沒有給定一個具體的案例。很多同學不知道 forwardRef具體怎麼用,下面我結合具體例子給大家講解forwardRef應用場景。
1 轉發引入 Ref
這個場景實際很簡單,比如父組件想獲取孫組件,某一個dom元素。這種隔代ref獲取引用,就需要forwardRef來助力。
function Son (props){
const { grandRef } = props
return <div>
<div> i am alien </div>
<span ref={grandRef} >這個是想要獲取元素</span>
</div>
}
class Father extends React.Component{
constructor(props){
super(props)
}
render(){
return <div>
<Son grandRef={this.props.grandRef} />
</div>
}
}
const NewFather = React.forwardRef((props,ref)=><Father grandRef={ref} {...props} /> )
class GrandFather extends React.Component{
constructor(props){
super(props)
}
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div>
<NewFather ref={(node)=> this.node = node } />
</div>
}
}
效果
forwaedRef.jpg
react不允許ref通過props傳遞,因爲組件上已經有 ref 這個屬性, 在組件調和過程中,已經被特殊處理,forwardRef出現就是解決這個問題,把ref轉發到自定義的forwardRef定義的屬性上,讓ref,可以通過props傳遞。
2 高階組件轉發 Ref
一文喫透hoc文章中講到,由於屬性代理的hoc,被包裹一層,所以如果是類組件,是通過ref拿不到原始組件的實例的,不過我們可以通過forWardRef轉發ref。
function HOC(Component){
class Wrap extends React.Component{
render(){
const { forwardedRef ,...otherprops } = this.props
return <Component ref={forwardedRef} {...otherprops} />
}
}
return React.forwardRef((props,ref)=> <Wrap forwardedRef={ref} {...props} /> )
}
class Index extends React.Component{
componentDidMount(){
console.log(666)
}
render(){
return <div>hello,world</div>
}
}
const HocIndex = HOC(Index,true)
export default ()=>{
const node = useRef(null)
useEffect(()=>{
/* 就可以跨層級,捕獲到 Index 組件的實例了 */
console.log(node.current.componentDidMount)
},[])
return <div><HocIndex ref={node} /></div>
}
如上,解決了高階組件引入Ref的問題。
lazy
React.lazy 和 Suspense 技術還不支持服務端渲染。如果你想要在使用服務端渲染的應用中使用,我們推薦 Loadable Components 這個庫
React.lazy和Suspense配合一起用,能夠有動態加載組件效果。React.lazy 接受一個函數,這個函數需要動態調用 import()。它必須返回一個 Promise ,該 Promise 需要 resolve 一個 default export 的 React 組件。
我們模擬一個動態加載的場景。
父組件
import Test from './comTest'
const LazyComponent = React.lazy(()=> new Promise((resolve)=>{
setTimeout(()=>{
resolve({
default: ()=> <Test />
})
},2000)
}))
class index extends React.Component{
render(){
return <div class style={ { marginTop :'50px' } } >
<React.Suspense fallback={ <div class ><SyncOutlined spin /></div> } >
<LazyComponent />
</React.Suspense>
</div>
}
}
我們用setTimeout來模擬import異步引入效果。Test
class Test extends React.Component{
constructor(props){
super(props)
}
componentDidMount(){
console.log('--componentDidMount--')
}
render(){
return <div>
<img src={alien} class />
</div>
}
}
效果
Suspense
何爲Suspense, Suspense 讓組件 “等待” 某個異步操作,直到該異步操作結束即可渲染。
用於數據獲取的 Suspense 是一個新特性,你可以使用 <Suspense> 以聲明的方式來 “等待” 任何內容,包括數據。本文重點介紹它在數據獲取的用例,它也可以用於等待圖像、腳本或其他異步的操作。
上面講到高階組件lazy時候,已經用 lazy + Suspense模式,構建了異步渲染組件。我們看一下官網文檔中的案例:
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 懶加載
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
Fragment
react不允許一個組件返回多個節點元素,比如說如下情況
render(){
return <li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
}
如果我們想解決這個情況,很簡單,只需要在外層套一個容器元素。
render(){
return <div>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</div>
}
但是我們不期望,增加額外的dom節點,所以react提供Fragment碎片概念,能夠讓一個組件返回多個元素。所以我們可以這麼寫
<React.Fragment>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</React.Fragment>
還可以簡寫成:
<>
<li> 🍎🍎🍎 </li>
<li> 🍌🍌🍌 </li>
<li> 🍇🍇🍇 </li>
</>
和Fragment區別是,Fragment可以支持key屬性。<></>不支持key屬性。
Profiler
Profiler這個api一般用於開發階段,性能檢測,檢測一次react組件渲染用時,性能開銷。
Profiler 需要兩個參數:
第一個參數:是 id,用於表識唯一性的Profiler。
第二個參數:onRender回調函數,用於渲染完成,接受渲染參數。
實踐:
const index = () => {
const callback = (...arg) => console.log(arg)
return <div >
<div >
<Profiler id="root" onRender={ callback } >
<Router >
<Meuns/>
<KeepaliveRouterSwitch withoutRoute >
{ renderRoutes(menusList) }
</KeepaliveRouterSwitch>
</Router>
</Profiler>
</div>
</div>
}
結果
onRender
0 -id: root -> Profiler 樹的 id 。1 -phase: mount -> mount 掛載 , update 渲染了。2 -actualDuration: 6.685000262223184 -> 更新 committed 花費的渲染時間。3 -baseDuration: 4.430000321008265 -> 渲染整顆子樹需要的時間 4 -startTime : 689.7299999836832 -> 本次更新開始渲染的時間 5 -commitTime : 698.5799999674782 -> 本次更新 committed 的時間 6 -interactions: set{} -> 本次更新的 interactions 的集合
儘管 Profiler 是一個輕量級組件,我們依然應該在需要時纔去使用它。對一個應用來說,每添加一些都會給 CPU 和內存帶來一些負擔。
StrictMode
StrictMode見名知意,嚴格模式,用於檢測react項目中的潛在的問題,。與 Fragment 一樣, StrictMode 不會渲染任何可見的 UI 。它爲其後代元素觸發額外的檢查和警告。
嚴格模式檢查僅在開發模式下運行;它們不會影響生產構建。
StrictMode目前有助於:
-
①識別不安全的生命週期。
-
②關於使用過時字符串
ref API的警告 -
③關於使用廢棄的
findDOMNode方法的警告 -
④檢測意外的副作用
-
⑤檢測過時的
context API
實踐: 識別不安全的生命週期
對於不安全的生命週期,指的是UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps , UNSAFE_componentWillUpdate
外層開啓嚴格模式:
<React.StrictMode>
<Router >
<Meuns/>
<KeepaliveRouterSwitch withoutRoute >
{ renderRoutes(menusList) }
</KeepaliveRouterSwitch>
</Router>
</React.StrictMode>
我們在內層組件中,使用不安全的生命週期:
class Index extends React.Component{
UNSAFE_componentWillReceiveProps(){
}
render(){
return <div class />
}
}
效果:
工具類
接下來我們一起來探究一下react工具類函數的用法。
createElement
一提到createElement,就不由得和JSX聯繫一起。我們寫的jsx,最終會被 babel,用createElement編譯成react元素形式。我寫一個組件,我們看一下會被編譯成什麼樣子,
如果我們在render裏面這麼寫:
render(){
return <div class >
<div class >生命週期</div>
<Text mes="hello,world" />
<React.Fragment> Flagment </React.Fragment>
{ /* */ }
text文本
</div>
}
會被編譯成這樣:
render() {
return React.createElement("div", { className: "box" },
React.createElement("div", { className: "item" }, "\u751F\u547D\u5468\u671F"),
React.createElement(Text, { mes: "hello,world" }),
React.createElement(React.Fragment, null, " Flagment "),
"text\u6587\u672C");
}
當然我們可以不用jsx模式,而是直接通過createElement進行開發。
createElement模型:
React.createElement(
type,
[props],
[...children]
)
createElement參數:
** 第一個參數:** 如果是組件類型,會傳入組件,如果是dom元素類型,傳入div或者span之類的字符串。
第二個參數:: 第二個參數爲一個對象,在dom類型中爲屬性,在組件類型中爲 props。
其他參數:,依次爲children,根據順序排列。
createElement 做了些什麼?
經過createElement處理,最終會形成 $$typeof = Symbol(react.element)對象。對象上保存了該react.element的信息。
cloneElement
可能有的同學還傻傻的分不清楚cloneElement和createElement區別和作用。
createElement把我們寫的jsx,變成element對象; 而cloneElement的作用是以 element 元素爲樣板克隆並返回新的 React 元素。返回元素的 props 是將新的 props 與原始元素的 props 淺層合併後的結果。
那麼cloneElement感覺在我們實際業務組件中,可能沒什麼用,但是在一些開源項目,或者是公共插槽組件中用處還是蠻大的,比如說,我們可以在組件中,劫持children element,然後通過cloneElement克隆element,混入props。經典的案例就是 react-router中的Swtich組件,通過這種方式,來匹配唯一的 Route並加以渲染。
我們設置一個場景,在組件中,去劫持children,然後給children賦能一些額外的props:
function FatherComponent({ children }){
const newChildren = React.cloneElement(children, { age: 18})
return <div> { newChildren } </div>
}
function SonComponent(props){
console.log(props)
return <div>hello,world</div>
}
class Index extends React.Component{
render(){
return <div class >
<FatherComponent>
<SonComponent />
</FatherComponent>
</div>
}
}
打印:
完美達到了效果!
createContext
createContext用於創建一個Context對象,createContext對象中,包括用於傳遞 Context 對象值 value的Provider,和接受value變化訂閱的Consumer。
const MyContext = React.createContext(defaultValue)
createContext接受一個參數defaultValue,如果Consumer上一級一直沒有Provider, 則會應用defaultValue作爲value。只有當組件所處的樹中沒有匹配到 Provider 時,其 defaultValue 參數纔會生效。
我們來模擬一個 Context.Provider和Context.Consumer的例子:
function ComponentB(){
/* 用 Consumer 訂閱, 來自 Provider 中 value 的改變 */
return <MyContext.Consumer>
{ (value) => <ComponentA {...value} /> }
</MyContext.Consumer>
}
function ComponentA(props){
const { name , mes } = props
return <div>
<div> 姓名: { name } </div>
<div> 想對大家說: { mes } </div>
</div>
}
function index(){
const [ value , ] = React.useState({
name:'alien',
mes:'let us learn React '
})
return <div style={{ marginTop:'50px' }} >
<MyContext.Provider value={value} >
<ComponentB />
</MyContext.Provider>
</div>
}
打印結果:
Provider和Consumer的良好的特性,可以做數據的存和取,Consumer一方面傳遞value, 另一方面可以訂閱value的改變。
Provider還有一個特性可以層層傳遞value,這種特性在react-redux中表現的淋漓盡致。
createFactory
React.createFactory(type)
返回用於生成指定類型 React 元素的函數。類型參數既可以是標籤名字符串(像是 'div'或'span'),也可以是 React 組件 類型 ( class 組件或函數組件),或是 React fragment 類型。
使用:
const Text = React.createFactory(()=><div>hello,world</div>)
function Index(){
return <div style={{ marginTop:'50px' }} >
<Text/>
</div>
}
效果
報出警告,這個api將要被廢棄,我們這裏就不多講了,如果想要達到同樣的效果,請用React.createElement
createRef
createRef可以創建一個 ref 元素,附加在react元素上。
用法:
class Index extends React.Component{
constructor(props){
super(props)
this.node = React.createRef()
}
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={this.node} > my name is alien </div>
}
}
個人覺得createRef這個方法,很雞肋,我們完全可以class類組件中這麼寫,來捕獲ref。
class Index extends React.Component{
node = null
componentDidMount(){
console.log(this.node)
}
render(){
return <div ref={(node)=> this.node } > my name is alien </div>
}
}
或者在function組件中這麼寫:
function Index(){
const node = React.useRef(null)
useEffect(()=>{
console.log(node.current)
},[])
return <div ref={node} > my name is alien </div>
}
isValidElement
這個方法可以用來檢測是否爲react element元素, 接受待驗證對象,返回true或者false。這個 api 可能對於業務組件的開發,作用不大,因爲對於組件內部狀態,都是已知的,我們根本就不需要去驗證,是否是react element 元素。但是,對於一起公共組件或是開源庫,isValidElement就很有作用了。
實踐
我們做一個場景,驗證容器組件的所有子組件,過濾到非react element類型。
沒有用isValidElement驗證之前:
const Text = () => <div>hello,world</div>
class WarpComponent extends React.Component{
constructor(props){
super(props)
}
render(){
return this.props.children
}
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
<Text/>
<div> my name is alien </div>
Let's learn react together!
</WarpComponent>
</div>
}
過濾之前的效果
我們用isValidElement進行react element驗證:
class WarpComponent extends React.Component{
constructor(props){
super(props)
this.newChidren = this.props.children.filter(item => React.isValidElement(item) )
}
render(){
return this.newChidren
}
}
過濾之後效果
過濾掉了非react element 的 Let's learn react together!。
Children.map
接下來的五個api都是和react.Chidren相關的,我們來分別介紹一下,我們先來看看官網的描述,React.Children 提供了用於處理 this.props.children 不透明數據結構的實用方法。
有的同學會問遍歷 children用數組方法,map ,forEach 不就可以了嗎?請我們注意一下不透明數據結構, 什麼叫做不透明結構?
我們先看一下透明的結構:
class Text extends React.Component{
render(){
return <div>hello,world</div>
}
}
function WarpComponent(props){
console.log(props.children)
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
<Text/>
<Text/>
<Text/>
<span>hello,world</span>
</WarpComponent>
</div>
}
打印
但是我們把Index結構改變一下:
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
{ new Array(3).fill(0).map(()=><Text/>) }
<span>hello,world</span>
</WarpComponent>
</div>
}
打印
這個數據結構,我們不能正常的遍歷了,即使遍歷也不能遍歷,每一個子元素。此時就需要 react.Chidren 來幫忙了。
但是我們把WarpComponent組件用react.Chidren處理children:
function WarpComponent(props){
const newChildren = React.Children.map(props.children,(item)=>item)
console.log(newChildren)
return newChildren
}
此時就能正常遍歷了,達到了預期效果。
注意如果 children 是一個 Fragment 對象,它將被視爲單一子節點的情況處理,而不會被遍歷。
Children.forEach
Children.forEach和Children.map 用法類似,Children.map可以返回新的數組,Children.forEach僅停留在遍歷階段。
我們將上面的WarpComponent方法,用Children.forEach改一下。
function WarpComponent(props){
React.Children.forEach(props.children,(item)=>console.log(item))
return props.children
}
Children.count
children 中的組件總數量,等同於通過 map 或 forEach 調用回調函數的次數。對於更復雜的結果,Children.count可以返回同一級別子組件的數量。
我們還是把上述例子進行改造:
function WarpComponent(props){
const childrenCount = React.Children.count(props.children)
console.log(childrenCount,'childrenCount')
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
{ new Array(3).fill(0).map((item,index) => new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果:
Children.toArray
Children.toArray返回,props.children扁平化後結果。
function WarpComponent(props){
const newChidrenArray = React.Children.toArray(props.children)
console.log(newChidrenArray,'newChidrenArray')
return newChidrenArray
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
{ new Array(3).fill(0).map((item,index)=>new Array(2).fill(1).map((item,index1)=><Text key={index+index1} />)) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果:
newChidrenArray , 就是扁平化的數組結構。React.Children.toArray() 在拉平展開子節點列表時,更改 key 值以保留嵌套數組的語義。也就是說, toArray 會爲返回數組中的每個 key 添加前綴,以使得每個元素 key 的範圍都限定在此函數入參數組的對象內。
Children.only
驗證 children 是否只有一個子節點(一個 React 元素),如果有則返回它,否則此方法會拋出錯誤。
不唯一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
{ new Array(3).fill(0).map((item,index)=><Text key={index} />) }
<span>hello,world</span>
</WarpComponent>
</div>
}
效果
唯一
function WarpComponent(props){
console.log(React.Children.only(props.children))
return props.children
}
function Index(){
return <div style={{ marginTop:'50px' }} >
<WarpComponent>
<Text/>
</WarpComponent>
</div>
}
效果
React.Children.only() 不接受 React.Children.map() 的返回值,因爲它是一個數組而並不是 React 元素。
react-hooks
對於react-hooks, 我已經寫了三部曲,對於常見的hooks,我這裏就不多講了,還沒看過的同學建議看一下react-hooks三部曲。
三部曲
玩轉 react-hooks, 自定義 hooks 設計模式及其實戰
我們今天重點說一下,幾個少用的api
useImperativeHandle
useImperativeHandle 可以配合 forwardRef自定義暴露給父組件的實例值。這個很有用,我們知道,對於子組件,如果是class類組件,我們可以通過ref獲取類組件的實例,但是在子組件是函數組件的情況,如果我們不能直接通過ref的,那麼此時useImperativeHandle和 forwardRef配合就能達到效果。
useImperativeHandle接受三個參數:
-
第一個參數 ref: 接受
forWardRef傳遞過來的ref。 -
第二個參數
createHandle:處理函數,返回值作爲暴露給父組件的ref對象。 -
第三個參數
deps: 依賴項deps,依賴項更改形成新的ref對象。
我們來模擬給場景,用useImperativeHandle,使得父組件能讓子組件中的input自動賦值並聚焦。
function Son (props,ref) {
console.log(props)
const inputRef = useRef(null)
const [ inputValue , setInputValue ] = useState('')
useImperativeHandle(ref,()=>{
const handleRefs = {
/* 聲明方法用於聚焦input框 */
onFocus(){
inputRef.current.focus()
},
/* 聲明方法用於改變input的值 */
onChangeValue(value){
setInputValue(value)
}
}
return handleRefs
},[])
return <div>
<input
placeholder="請輸入內容"
ref={inputRef}
value={inputValue}
/>
</div>
}
const ForwarSon = forwardRef(Son)
class Index extends React.Component{
cur = null
handerClick(){
const { onFocus , onChangeValue } =this.cur
onFocus()
onChangeValue('let us learn React!')
}
render(){
return <div style={{ marginTop:'50px' }} >
<ForwarSon ref={cur => (this.cur = cur)} />
<button onClick={this.handerClick.bind(this)} >操控子組件</button>
</div>
}
}
效果:
useDebugValue
useDebugValue 可用於在 React 開發者工具中顯示自定義 hook 的標籤。這個hooks目的就是檢查自定義hooks
function useFriendStatus(friendID) {
const [isOnline, setIsOnline] = useState(null);
// ...
// 在開發者工具中的這個 Hook 旁邊顯示標籤
// e.g. "FriendStatus: Online"
useDebugValue(isOnline ? 'Online' : 'Offline');
return isOnline;
}
我們不推薦你向每個自定義 Hook 添加 debug 值。當它作爲共享庫的一部分時才最有價值。在某些情況下,格式化值的顯示可能是一項開銷很大的操作。除非需要檢查 Hook,否則沒有必要這麼做。因此,useDebugValue 接受一個格式化函數作爲可選的第二個參數。該函數只有在 Hook 被檢查時纔會被調用。它接受 debug 值作爲參數,並且會返回一個格式化的顯示值。
useTransition
useTransition允許延時由state改變而帶來的視圖渲染。避免不必要的渲染。它還允許組件將速度較慢的數據獲取更新推遲到隨後渲染,以便能夠立即渲染更重要的更新。
const TIMEOUT_MS = { timeoutMs: 2000 }
const [startTransition, isPending] = useTransition(TIMEOUT_MS)
-
useTransition接受一個對象,timeoutMs代碼需要延時的時間。 -
返回一個數組。第一個參數: 是一個接受回調的函數。我們用它來告訴
React需要推遲的state。第二個參數: 一個布爾值。表示是否正在等待,過度狀態的完成 (延時state的更新)。
下面我們引入官網的列子,來了解useTransition的使用。
const SUSPENSE_CONFIG = { timeoutMs: 2000 };
function App() {
const [resource, setResource] = useState(initialResource);
const [startTransition, isPending] = useTransition(SUSPENSE_CONFIG);
return (
<>
<button
disabled={isPending}
onClick={() => {
startTransition(() => {
const nextUserId = getNextId(resource.userId);
setResource(fetchProfileData(nextUserId));
});
}}
>
Next
</button>
{isPending ? " 加載中..." : null}
<Suspense fallback={<Spinner />}>
<ProfilePage resource={resource} />
</Suspense>
</>
);
}
在這段代碼中,我們使用 startTransition 包裝了我們的數據獲取。這使我們可以立即開始獲取用戶資料的數據,同時推遲下一個用戶資料頁面以及其關聯的 Spinner 的渲染 2 秒鐘( timeoutMs 中顯示的時間)。
這個api目前處於實驗階段,沒有被完全開放出來。
react-dom
接下來,我們來一起研究react-dom中比較重要的api。
render
render 是我們最常用的react-dom的 api,用於渲染一個react元素,一般react項目我們都用它,渲染根部容器app。
ReactDOM.render(element, container[, callback])
使用
ReactDOM.render(
< App / >,
document.getElementById('app')
)
ReactDOM.render會控制container容器節點裏的內容,但是不會修改容器節點本身。
hydrate
服務端渲染用hydrate。用法與 render() 相同,但它用於在 ReactDOMServer 渲染的容器中對 HTML 的內容進行 hydrate 操作。
ReactDOM.hydrate(element, container[, callback])
createPortal
Portal 提供了一種將子節點渲染到存在於父組件以外的 DOM 節點的優秀的方案。createPortal 可以把當前組件或 element 元素的子節點,渲染到組件之外的其他地方。
那麼具體應用到什麼場景呢?
比如一些全局的彈窗組件model,<Model/>組件一般都寫在我們的組件內部,倒是真正掛載的dom,都是在外層容器,比如body上。此時就很適合createPortalAPI。
createPortal接受兩個參數:
ReactDOM.createPortal(child, container)
第一個:child 是任何可渲染的 React 子元素第二個:container是一個 DOM 元素。
接下來我們實踐一下:
function WrapComponent({ children }){
const domRef = useRef(null)
const [ PortalComponent, setPortalComponent ] = useState(null)
React.useEffect(()=>{
setPortalComponent( ReactDOM.createPortal(children,domRef.current) )
},[])
return <div>
<div class ref={ domRef } ></div>
{ PortalComponent }
</div>
}
class Index extends React.Component{
render(){
return <div style={{ marginTop:'50px' }} >
<WrapComponent>
<div >hello,world</div>
</WrapComponent>
</div>
}
}
效果
我們可以看到,我們children實際在container 之外掛載的,但是已經被createPortal渲染到container中。
unstable_batchedUpdates
在react-legacy模式下,對於事件,react事件有批量更新來處理功能, 但是這一些非常規的事件中,批量更新功能會被打破。所以我們可以用react-dom中提供的unstable_batchedUpdates 來進行批量更新。
一次點擊實現的批量更新
class Index extends React.Component{
constructor(props){
super(props)
this.state={
numer:1,
}
}
handerClick=()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
}
render(){
return <div style={{ marginTop:'50px' }} >
<button onClick={ this.handerClick } >click me</button>
</div>
}
}
效果
渲染次數一次。
批量更新條件被打破
handerClick=()=>{
Promise.resolve().then(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
}
效果
渲染次數三次。
unstable_batchedUpdate 助力
handerClick=()=>{
Promise.resolve().then(()=>{
ReactDOM.unstable_batchedUpdates(()=>{
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
this.setState({ numer : this.state.numer + 1 })
console.log(this.state.numer)
})
})
}
渲染次數一次, 完美解決批量更新問題。
flushSync
flushSync 可以將回調函數中的更新任務,放在一個較高的優先級中。我們知道react設定了很多不同優先級的更新任務。如果一次更新任務在flushSync回調函數內部,那麼將獲得一個較高優先級的更新。比如
ReactDOM.flushSync(()=>{
/* 此次更新將設置一個較高優先級的更新 */
this.setState({ name: 'alien' })
})
爲了讓大家理解flushSync,我這裏做一個demo奉上,
/* flushSync */
import ReactDOM from 'react-dom'
class Index extends React.Component{
state={ number:0 }
handerClick=()=>{
setTimeout(()=>{
this.setState({ number: 1 })
})
this.setState({ number: 2 })
ReactDOM.flushSync(()=>{
this.setState({ number: 3 })
})
this.setState({ number: 4 })
}
render(){
const { number } = this.state
console.log(number) // 打印什麼??
return <div>
<div>{ number }</div>
<button onClick={this.handerClick} >測試flushSync</button>
</div>
}
}
先不看答案,點擊一下按鈕,打印什麼呢?
我們來點擊一下看看
打印 0 3 4 1 ,相信不難理解爲什麼這麼打印了。
-
首先
flushSyncthis.setState({ number: 3 })設定了一個高優先級的更新,所以 3 先被打印 -
2 4 被批量更新爲 4
相信這個demo讓我們更深入瞭解了flushSync。
findDOMNode
findDOMNode用於訪問組件DOM元素節點,react推薦使用ref模式,不期望使用findDOMNode。
ReactDOM.findDOMNode(component)
注意的是:
-
1
findDOMNode只能用在已經掛載的組件上。 -
2 如果組件渲染內容爲
null或者是false,那麼findDOMNode返回值也是null。 -
3
findDOMNode不能用於函數組件。
接下來讓我們看一下,findDOMNode具體怎麼使用的:
class Index extends React.Component{
handerFindDom=()=>{
console.log(ReactDOM.findDOMNode(this))
}
render(){
return <div style={{ marginTop:'100px' }} >
<div>hello,world</div>
<button onClick={ this.handerFindDom } >獲取容器dom</button>
</div>
}
}
效果:
我們完全可以將外層容器用ref來標記,獲取捕獲原生的dom節點。
unmountComponentAtNode
從 DOM 中卸載組件,會將其事件處理器和 state 一併清除。如果指定容器上沒有對應已掛載的組件,這個函數什麼也不會做。如果組件被移除將會返回 true ,如果沒有組件可被移除將會返回 false 。
我們來簡單舉例看看unmountComponentAtNode如何使用?
function Text(){
return <div>hello,world</div>
}
class Index extends React.Component{
node = null
constructor(props){
super(props)
this.state={
numer:1,
}
}
componentDidMount(){
/* 組件初始化的時候,創建一個 container 容器 */
ReactDOM.render(<Text/> , this.node )
}
handerClick=()=>{
/* 點擊卸載容器 */
const state = ReactDOM.unmountComponentAtNode(this.node)
console.log(state)
}
render(){
return <div style={{ marginTop:'50px' }} >
<div ref={ ( node ) => this.node = node } ></div>
<button onClick={ this.handerClick } >click me</button>
</div>
}
}
效果
總結
本文通過react組件層面,工具層面,hooks層面,react-dom瞭解了api的用法,希望看完的同學,能夠對着文章中的demo自己敲一遍,到頭來會發現自己成長不少。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/wdChmE2-UDNFVyzG7RV0sg