父子組件通信
-
props -父組件向子組件傳遞數(shù)據(jù)- 父組件
import Header from './Header' // 引入子組件Header this.state = { msg:'123' } run= () => { // ... } render() { return(<div><Header msg={this.state.msg} /><div>) } - 子組件
Header.jsconstructor(props) { // props 是一個對象,用于接收父組件的數(shù)據(jù) super(props) } render() { return(<div> <span>{this.props.msg}<span> <button onClick={this.props.run}>Run Test</button> <div>) }
- 父組件
-
Context API -跨層級通信,祖代組件向任意層級的后代組件傳遞
ps:Vue中的provide/inject來源于Context
Context模式下有兩個角色:Provider -外層提供數(shù)據(jù)的組件,Consumer -內(nèi)存獲取數(shù)據(jù)的組件。- 創(chuàng)建
Context上下文,放在一個文件中,方便引用
當// storecontext.js import React from 'react' // Context 可以創(chuàng)建多個! const StoreContext = React.createContext(/*可以設(shè)置默認值*/) export default StoreContextReact渲染一個訂閱Context對象的組件時,這個組件會從組件樹中離自身最近的那個匹配的Provider中讀取到當前的context值;只有當組件所在的樹中沒有匹配到Provider時,設(shè)置的默認值才會生效。這有助于在不使用Provider包裝組件的情況下,對組件進行測試。 - 在父組件中使用
Provider,它接收一個value屬性,當value值發(fā)生變化時,它內(nèi)部的所有消費組件都會重新渲染import StoreContext from './StoreContext' // 創(chuàng)建一個數(shù)據(jù)源 const store = { name: 'Tom', age: 20 } render() { return( <StoreContext.Provider value={store}> <AAA></AAA> </StoreContext.Provider> ) } - 在后代組件中使用
Consumer,它包裹一個函數(shù),回調(diào)的參數(shù)正是距離自己最近的Provider提供的數(shù)據(jù)源(value);如果沒有找到Provider,則回調(diào)createContext()的默認值;import StoreContext from './StoreContext' render() { return(<StoreContext.Consumer> { value => { return(<div> <p>姓名: {value.name}</p> <p>年齡: {value.age}</p> </div>) } } </StoreContext.Consumer>) } # 作為 props 直接把Consumer的值傳遞給組件 render() { return(<StoreContext.Consumer> { ctx => <TitleBar {...ctx} /> } </StoreContext.Consumer>) } // 函數(shù)式組件 function TitleBar(props) { console.log(props) return <div>TitleBar</div> } -
Class.contextType實現(xiàn)編程式消費最近Context的值,可以在任何生命周期中訪問它import StoreContext from './StoreContext' class Titlebar extends React.Component { componentDidMount() { // StoreContext 共享的值被賦予 this.context let value = this.context; } render() { let value = this.context; // ... } } Titlebar.contextType = StoreContext;
- 創(chuàng)建
-
refs -父組件主動獲取子組件的數(shù)據(jù)
React提供了refs管理組件標簽上的自定義屬性ref;<Header ref="header" /> this.refs.header // 訪問子組件的數(shù)據(jù) 發(fā)布訂閱,第三方包如
pubsub-js-
defaultProps與propTypes- 父組件在調(diào)用子組件時,如果不給子組件傳值,則子組件可以使用
defaultProps定義默認值;class Header extends React.Component { ... } Header.defaultProps = { msg: '0' //如果父組件沒有傳遞msg,則使用此默認值 } export default Header; -
propTypes:用于驗證父組件傳值類型的合法性;import PropTypes from 'prop-types' //react的一個內(nèi)置模塊 Header.propTypes = { msg: PropTypes.number //父組件傳遞msg的數(shù)據(jù)類型必須是number }
- 父組件在調(diào)用子組件時,如果不給子組件傳值,則子組件可以使用
-
組件
state/props的劃分原則- 讓組件盡可能少狀態(tài)
對于只有UI渲染、數(shù)據(jù)展示、沒有復(fù)雜交互的組件,應(yīng)使用props,而不是state - 組件隨著時間產(chǎn)生變化的數(shù)據(jù),界面有交互,應(yīng)使用
state
- 讓組件盡可能少狀態(tài)
ref
-
React v16.3引入了React.createRef()創(chuàng)建Ref,字符串類型的Ref將慢慢被拋棄;class MyComponent extends React.Component { constructor(props) { super(props); this.myRef = React.createRef(); } render() { return <div ref={this.myRef} />; } }- React 會在組件加載時設(shè)置
ref的current屬性,在卸載時則會改回null; -
ref的更新會發(fā)生在鉤子componentDidMount()或componentDidUpdate()之前。
- React 會在組件加載時設(shè)置
- 訪問
const node = this.myRef.current;current的值取決于節(jié)點的類型- 普通
HTML元素,值為DOM對象 - React組件,值為實例對象
- 普通
- 函數(shù)式組件上不能使用
ref屬性,因為它沒有實例,但它的內(nèi)部可以使用ref屬性; -
React v16.3推薦使用Ref轉(zhuǎn)發(fā),從而把子組件的Ref暴漏給父組件; - 回調(diào)式
Ref
這種設(shè)置ref的方式可以更加細致地控制ref的設(shè)置與解除。class CustomTextInput extends React.Component { constructor(props) { super(props); this.textInput = null; } this.setTextInputRef = element => { this.textInput = element; } render() { return <input type="text" ref={this.setTextInputRef} /> } }-
ref屬性接收一個函數(shù),在組件掛載時回調(diào)ref函數(shù),當卸載時傳入null并回調(diào); - 在組件間傳遞以回調(diào)的形式傳遞
refsclass Parent extends React.Component { render() { return <CustomTextInput inputRef={el => this.inputElement = el} /> } } function CustomTextInput(props) { return <input ref={props.inputRef} /> }
-
生命周期
React V16.3之前的生命周期



-
this.forceUpdate()手動強制觸發(fā)組件的render渲染,會導(dǎo)致組件跳過shouldComponentUpdate(),直接調(diào)用render();
應(yīng)用場景:有些變量不在state上,或者state里的某個變量層次太深,更新時沒有自動觸發(fā)render()。 -
React V16.0引入新的生命周期:componentDidCatch(),如果render()函數(shù)拋出錯誤,該函數(shù)勾子可以捕捉到錯誤信息,且可以展示相應(yīng)的錯誤提示。
注:它只是一個增量式的修改,完全不影響原有的生命周期勾子。
- 組件首次加載過程中觸發(fā)的函數(shù)
constructor --> componentWillMount --> render --> componentDidMount-
componentWillMount():組件將要掛載 -
render():數(shù)據(jù)/模板渲染 -
componentDidMount():組件加載完成 -
constructor()和render()在組件加載時會被觸發(fā),但并不屬于生命周期函數(shù)
-
- 組件狀態(tài)更新過程中觸發(fā)的函數(shù)
shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate-
shouldComponentUpdate():是否更新組件,常用于做組件優(yōu)化,返回true則更新,否則不更新。shouldComponentUpdate(nextProps, nextState) { // nextProps:表示父組件向當前組件傳遞的數(shù)據(jù) // nextState:當前組件更新后的數(shù)據(jù),只是還沒有重新渲染DOM } -
componentWillUpdate():將要更新組件; -
render():更新數(shù)據(jù)/模板; -
componentDidUpdate():數(shù)據(jù)更新完成。
-
- 在父組件中改變
props傳值時觸發(fā)的函數(shù):componentWillReceiveProps(),然后才會觸發(fā)組件更新的render()函數(shù)。 - 組件銷毀時觸發(fā)的函數(shù):
componentWillUnmount()- 父組件
this.state = { flag:true } setFlag() { this.setState({ flag:!this.state.flag }) } render() { return(<div> { this.state.flag && <Header /> } <button onClick={this.setFlag}>掛載與銷毀</button> </div>) } - 子組件Header
componentWillUnmount() { console.log('Header被銷毀') }
- 父組件
- 手動移除某個容器上的組件:
ReactDOM.unmountComponentAtNode(containerDom)
React V16.3的新生命周期
React V16.3的更新中,除了被熱烈討論的新Context API之外,新引入的兩個生命周期函數(shù)static getDerivedStateFromProps、getSnapshotBeforeUpdate以及在未來V17.0版本中即將被移除的三個生命周期函數(shù)componentWillMount、componentWillReceiveProps、componentWillUpdate。這三個準備廢棄的生命周期用getDerivedStateFromProps替代。如果仍想使用廢棄的生命周期,可以加上前綴:UNSAFE_,如UNSAFE_componentWillMount。

原來的生命周期在 React v16 推出 Fiber 之后就不合適了,因為如果開啟 async rendering,在 render() 之前的所有函數(shù)都可能被執(zhí)行多次。
在 render() 之前執(zhí)行的生命周期:componentWillMount、componentWillReceiveProps、shouldComponentUpdate、componentWillUpdate
除了 shouldComponentUpdate,其他三個都被靜態(tài)函數(shù)getDerivedStateFromProps取代。
這種做法強制開發(fā)者在 render() 執(zhí)行之前只做無副作用的操作,而且能做的操作局限在根據(jù) props 和 state 決定新的state。
-
state getDerivedStateFromProps(props, state)會在render()之前被調(diào)用,并且在初始化掛載及后續(xù)更新時都會被調(diào)用,返回一個對象來更新state,如果返回null則不執(zhí)行更新。 -
getSnapshotBeforeUpdate(prevProps, prevState)在render()之后、componentDidUpdate()之前。使得組件能在發(fā)生更改之前從DOM中獲取一些信息(如滾動位置)。返回值將作為參數(shù)snapshot傳遞給componentDidUpdate(prevProps, prevState, snapshot)
錯誤邊界
默認清空下,若一個組件在渲染期間(render)發(fā)生錯誤,會導(dǎo)致整個組件樹全部被卸載。
錯誤邊界: 是一個組件,它能夠捕獲到渲染期間子組件發(fā)生的錯誤,并有能力阻止錯誤繼續(xù)傳播。
錯誤邊界能捕獲在渲染過程中 所有子組件的constructor和生命周期函數(shù)內(nèi)發(fā)生的錯誤。
錯誤邊界不能捕獲的錯誤類型:
- 發(fā)生在事件處理器里面的
- 異步代碼,如
setTimeout、requestAnimationFrame - 服務(wù)端渲染
- 自己本身拋出的錯誤
在代碼層面上,只要一個類組件中定義了 static getDerivedStateFromError() 或者 componentDidCatch(),它就是一個錯誤邊界(組件)。
一般來說,getDerivedStateFromError()是不允許發(fā)生副作用的,故而負責(zé)呈現(xiàn)一個備用的Fallback UI給用戶;componentDidCatch()允許發(fā)生副作用,故負責(zé)打印錯誤日志,上報錯誤到遠程服務(wù)器。
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// Update state so the next render will show the fallback UI.
return { hasError: true };
}
componentDidCatch(error, info) {
// You can also log the error to an error reporting service
// error 表示被拋出的錯誤
// info 表示一個含有 ```componentStack``` 屬性的對象
logErrorToMyService(error, info);
}
render() {
if (this.state.hasError) {
// You can render any custom fallback UI
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
然后可以把它當做一個普通的組件來用:
<ErrorBoundary>
<MyWidget />
</ErrorBoundary>
<ErrorBoundary>使用起來就像try-catch語句,只不過它是用于React組件,且保留了React原生的聲明特性。
注意:
<ErrorBoundary>不能捕獲自己所產(chǎn)生的錯誤,只能捕獲在它之下的組件樹所產(chǎn)生的錯誤。
在<ErrorBoundary>嵌套使用的情況下,如果某個<ErrorBoundary>不能渲染一些錯誤信息(調(diào)用static getDerivedStateFromError()失?。敲催@個錯誤就會往上冒泡到層級最近的<ErrorBoundary>。這也是try-catch語句在Javascript里的執(zhí)行機制。