本文是『horseshoe·React專題』系列文章之一,後續會有更多專題推出
來我的 GitHub repo 閱讀完整的專題文章
來我的 個人部落格 獲得無與倫比的閱讀體驗
React是用來解決狀態同步的,但它卻有一個與this.state
並駕齊驅的概念。
這就是this.props
。
this.props
是元件之間溝通的一個介面。
原則上來講,它只能從父元件流向子元件,但是開發者有各種hack技巧,基本上近親之間溝通是不成問題的。
this.props
this.props
是一個極其簡單的介面。世界需要更多這樣的傻瓜介面
你只需要像寫HTML標籤的屬性一樣,把它寫上去,它就傳到了子元件的this.props
裡面。
不過有幾個地方需要注意:
- 有兩個特殊的屬性
ref
和key
,它們各有用途,並不會傳給子元件的this.props
。 - 如果只給屬性不給值,React會預設解析成布林值
true
。 - 除了字串,其他值都要用花括號包裹。
- 如果你把屬性給了標籤而不是子元件,React並不會解析。
import React, { Component, createRef } from `react`;
import Child from `./Child`;
class App extends Component {
isPopular = false;
refNode = createRef();
render() {
return [
<Child key="react" ref={this.refNode} isPopular />,
<Child key="vue" url="https://github.com/vuejs/vue" star={96500} />,
<Child key="angular" owner="google" isPopular={this.isPopular} />,
];
}
}
export default App;
複製程式碼
this.props
是一個不可變物件
React具有濃重的函數語言程式設計的思想。
提到函數語言程式設計就要提一個概念:純函式。
純函式有幾個特點:
- 給定相同的輸入,總是返回相同的輸出。
- 過程沒有副作用。
- 不依賴外部狀態。
function doSomething(a, b) {
return a + b;
}
複製程式碼
這是一種程式設計思想。如果你對這個概念有點模糊,我可以舉個例子:
你的殺父仇人十年後突然現身,於是你決定僱傭一個冷麵殺手去解決他。
你會找一個什麼樣的殺手呢?
- 給多少錢辦多少事,效果可預期,從不失手。
- 不誤傷百姓,不引起動靜。
- 沒有團伙,單獨作案,乾淨利落,便於封口。
如果你面對殺父仇人有這樣的覺悟,那麼純函式便是你的囊中之物了。
為什麼要提純函式?因為this.props
就是汲取了純函式的思想。
它最大的特點就是不可變。
跟this.state
不一樣的是,this.props
來真的。雖然this.state
也反對開發者直接改變它的屬性,但畢竟只是嘴上說說,還是要靠開發者自己的約束。然而this.props
會直接讓你的程式崩潰。
加上React也沒有this.setProps
方法,所以不需要開發者自我約束,this.props
就是不可變的。
溝通基本靠吼
父元件給子元件傳值
這個無需贅言,最直觀的傳值方式。
import React from `react`;
import Child from `./Child`;
const App = () => {
return (
<Child star={1000} />
);
}
export default App;
複製程式碼
子元件給父元件傳值
其實就是利用回撥函式的引數傳遞值。
父元件定義一個方法,將該方法通過props傳給子元件,子元件需要給父元件傳值時,便傳參執行該方法。由於方法定義在父元件裡,父元件可以接收到該值。
import React, { Component } from `react`;
import Child from `./Child`;
class App extends Component {
state = { value: `` };
render() {
return (
<Child handleSomething={this.handleSomething} />
);
}
handleSomething = (e) => {
this.setState({ value: e.target.value });
}
}
export default App;
複製程式碼
import React from `react`;
const Child = (props) => {
return (
<input type="text" onChange={props.handleSomething} />
);
}
export default Child;
複製程式碼
兄弟元件之間傳值
原理和回撥函式一樣,只不過這裡父元件只是一個橋樑。
父元件接收到回撥函式的值以後,通過this.setState
儲存該值,並觸發另一個子元件重新渲染,重新渲染後另一個子元件便可以獲得該值。
import React, { Component, Fragment } from `react`;
import ChildA from `./ChildA`;
import ChildB from `./ChildB`;
class App extends Component {
state = { value: `` };
render() {
return (
<Fragment>
<ChildA handleSomething={this.handleSomething} />
<ChildA value={this.state.value} />
</Fragment>
);
}
handleSomething = (e) => {
this.setState({ value: e.target.value });
}
}
export default App;
複製程式碼
import React from `react`;
const ChildA = (props) => {
return (
<input type="text" onChange={props.handleSomething} />
);
}
export default ChildA;
複製程式碼
import React from `react`;
const ChildB = (props) => {
return (
<div>{props.value}</div>
);
}
export default ChildB;
複製程式碼
createContext
?這是React v16.3.0釋出的API。
React為開發者提供了一扇傳送門,它就是Context物件。
嚴格來說,Context早就存在於React中了,不過一直以來都不是正式的API。
終於在v16.3.0轉正了。
為什麼說Context是一扇傳送門?因為它可以跨元件傳遞資料。不是父子之間的小打小鬧哦,而是可以跨任意層級。但是有一個限制,資料只能向下傳遞,原因就是後面要講到的單向資料流。
開發者通過createContext
建立一個上下文物件(React特別喜歡create),然後找一個頂級元件作為Provider
。接下來就可以在任意下級元件消費它提供的資料了。
- 只要
Provider
的資料改變,就會觸發Consumer
的更新。 - 建立時可以提供一個預設值,另外掛載時可以通過value屬性傳遞資料。但是預設值只有在不提供
Provider
的情況下才起作用。 - 開發者可以建立多個Context。
Consumer
的children必須是一個函式。
舊的Context存在一個問題,如果接收元件的shouldComponentUpdate
生命週期鉤子返回false,則它不會接收到Context中的資料,因為它是通過this.props
一級一級往下傳的。
而新的Context採取的是訂閱釋出模式,所以不存在這個問題。
實際上react-redux庫的Provider
元件內部就是使用了舊的Context API,不過redux做了一些優化。
import { createContext } from `react`;
const { Provider, Consumer } = createContext({ lang: `en` });
export { Provider, Consumer };
複製程式碼
import React, { Component } from `react`;
import { Provider } from `./context`;
import Todos from `./Todos`;
const App = () => {
return (
<Provider value={{ lang: `zh` }}>
<Todos />
</Provider>
);
}
export default App;
複製程式碼
import React, { Fragment } from `react`;
import TodoItem from `./TodoItem`;
const Todos = () => {
return (
<Fragment>
<TodoItem />
<TodoItem />
<TodoItem />
</Fragment>
);
}
export default Todos;
複製程式碼
import React from `react`;
import { Consumer } from `./context`;
const TodoItem = () => {
return (
<Consumer>
{({ lang }) => <div>{lang === `en` ? `todo` : `要做`}</div>}
</Consumer>
);
}
export default TodoItem;
複製程式碼
單向資料流
水往低處流,這是自然規律。
React通過描述狀態來控制UI的表達,這就涉及到UI的更新機制。
狀態除了內部狀態之外,肯定有一些狀態是要元件之間共享的,所以,一旦一個元件的狀態更新了,可能會牽扯到很多元件的更新,框架的更新機制必將變的異常複雜。
但是迴歸到水的意象,如果狀態的流向是單向的,而且是自上往下流動,這就變的非常符合直覺,而且更新機制可以做到極簡:我更新,則我的所有下級也更新。
這就是this.props
的思想源頭。
它雖然叫props,但它也是狀態,只不過是共享的狀態。
它只能自頂向下流動。
內部不能改變this.props
。
某個props的源頭更新了,則流經的所有元件都要更新,除非開發者手動禁止。
脈絡清晰,this.props
才是賦予了React血液的東西。
關於React摒棄了表單雙向資料繫結的問題,它只是想把單向資料流做的更徹底一點。其實表單的狀態,歸根結底是元件內部的狀態,跟單向資料流無關。
什麼是雙向資料繫結?就是表單輸入,與之繫結的變數自動獲取到輸入的值,變數的值改變,與之繫結的表單的值隨即改變,兩種流向都自動繫結了。
但其實雙向資料繫結不就是value的單向繫結加onChange事件監聽麼!React也可以通過兩步做到。
總結:雙向資料繫結不影響單向資料流,React也可以實現雙向的同步。
React專題一覽