Github: github.com/gaoljie/rea…
函式元件(Functional Component)
函式元件是純 UI 元件,也稱作傻瓜元件, 或者無狀態元件。渲染所需要的資料只通過 props 傳入, 不需要用 class 的方式來建立 React 元件, 也不需要用到 this 關鍵字,或者用到 state
函式元件具有以下優點
- 可讀性好
- 邏輯簡單
- 測試簡單
- 程式碼量少
- 容易複用
- 解耦
const Hello = props => <div>Hello {props.name}</div>;
const App = props => <Hello name={"world"} />;
複製程式碼
什麼情況下不使用函式元件? 如果你需要用到 react 生命週期, 需要用到 state, 函式元件就沒辦法滿足要求了。 但是新的 Hook 特性出來之後又大大提升了函式元件的應用範圍, 所以沒有最佳的設計模式,都是要因地制宜。
Render Props
給某個元件通過 props 傳遞一個函式,並且函式會返回一個 React 元件,這就是 render props.
const Hello = props => <div>{props.render({ name: "World" })}</div>;
const App = props => <Hello render={props => <h1>Hello {props.name}</h1>} />;
複製程式碼
你也可以把函式放在元件 tag 的中間,元件可以通過props.children
獲取
const Hello = props => <div>{props.children({ name: "World" })}</div>;
const App = props => <Hello>{props => <h1>Hello {props.name}</h1>}</Hello>;
//複用
const App2 = props => <Hello>{props => <h1>Hey {props.name}</h1>}</Hello>;
複製程式碼
render props 提高了元件的複用性和靈活性, 相比元件直接渲染特定模板,通過 render props,父元件自定義需要的模板,子元件只要呼叫父元件提供的函式, 傳入需要的資料就可以了。
高階元件(HOC)
高階元件是一個接受 Component 並返回新的 Component 的函式。之所以叫高階元件是因為它和函數語言程式設計中的高階函式類似, 一個接受函式為引數, 或者返回值為函式的函式便稱作高階函式.
const Name = props => <span>{props.children}</span>;
const reverse = Component => {
return ({ children, ...props }) => (
<Component {...props}>
{children
.split("")
.reverse()
.join("")}
</Component>
);
};
const ReversedName = reverse(Name);
const App = props => <ReversedName>hello world</ReversedName>;
複製程式碼
reverse 函式接受一個 Component 引數,返回一個可以 reverse 內容的新的函式式元件。這個高階元件封裝了 reverse 方法,以後每次需要 reverse 某些元件的內容就沒必要重複寫一下步驟:
const Name = props => <span>{props.children}</span>;
const App = props => (
<Name>
{"hello world"
.split("")
.reverse()
.join("")}
</Name>
);
複製程式碼
組合元件(Compound Components)
組合元件設計模式一般應用在一些共享元件上。 如 <select>
和<option>
, <Tab>
和<TabItem>
等,通過組合元件,使用者只需要傳遞子元件,子元件所需要的props
在父元件會封裝好,引用子元件的時候就沒必要傳遞所有props
了。 比如下面的例子,每個 ListItem 需要一個index
引數來顯示第幾項, 可以使用下面的方式渲染
const List = ({ children }) => <ul>{children}</ul>;
const ListItem = ({ children, index }) => (
<li>
{index} {children}
</li>
);
const App = props => (
<List>
<ListItem index={1}>apple</ListItem>
<ListItem index={2}>banana</ListItem>
</List>
);
複製程式碼
這種方式存在一些問題, 每次新增加一個列表項都要手動傳一個index
值,如果以後要加其他的屬性, 就需要每一項都修改,組合元件可以避免上述的缺陷。
const List = ({children}) => (
<ul>
{React.Children.map(children, (child, index) => React.cloneElement(child, {
index: index
}))}
</ul>
)
const ListItem = ({children, index}) => (
<li>{index} {children}</li>
)
<List>
<ListItem>apple</ListItem>
<ListItem>banana</ListItem>
</List>
複製程式碼
如果把 ListItem 通過static
方式放在 List 元件裡面,更具語義化。
class List extends Component {
static Item = ({ children, index }) => (
<li>
{index} {children}
</li>
);
render() {
return (
<ul>
{React.Children.map(this.props.children, (child, index) =>
React.cloneElement(child, {
index: index
})
)}
</ul>
);
}
}
const App = props => (
<List>
<List.Item>apple</List.Item>
<List.Item>banana</List.Item>
</List>
);
複製程式碼
提供者模式(Provider Pattern)
提供者模式可以解決非父子元件下的資訊傳遞問題, 或者元件層級太深需要層層傳遞的問題
const Child = ({ lastName }) => <p>{lastName}</p>;
const Mother = ({ lastName }) => <Child lastName={lastName} />;
const GrandMother = ({ lastName }) => <Mother lastName={lastName} />;
const App = props => <GrandMother lastName={"Kieffer"} />;
複製程式碼
上面的例子lastName
需要在每個元件都傳遞一次,提供者模式就可以避免這種 Prop Drilling 的寫法
const FamilyContext = React.createContext({});
const FamilyProvider = FamilyContext.Provider;
const FamilyConsumer = FamilyContext.Consumer;
const Child = ({ lastName }) => (
<FamilyConsumer>{context => <p>{context}</p>}</FamilyConsumer>
);
const Mother = () => <Child />;
const GrandMother = () => <Mother />;
const App = props => (
<FamilyProvider value={"Kieffer"}>
<GrandMother />
</FamilyProvider>
);
複製程式碼
State Reducer
State Reducer可以讓父元件控制子元件state。render props 可以控制子元件的UI是如何渲染的,state reducer則可以控制子元件的state.
下面的例子,通過傳入state reducer方法,父元件可以控制子元件最多隻點選4次。
class Counter extends Component{
state = {
count: 0
}
setInternalState = (stateOrFunc, callback) => {
this.setState(state => {
const changedState = typeof stateOrFunc === 'function'
? stateOrFunc(state)
: stateOrFunc
const reducedState = this.props.stateReducer(state, changedState) || {}
// return null when reducedState is an empty object, prevent rerendering
return Object.keys(reducedState).length > 0
? reducedState
: null
}, callback)
}
addCount = () => this.setInternalState(state => ({count: state.count + 1}))
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={this.addCount}>
Click me
</button>
</div>
);
}
}
const App = props => {
const stateReducer = (state, change) => {
if (state.count >= 4) {
return {}
}
return change
}
return <Counter stateReducer={stateReducer}/>
}
複製程式碼
Controlled Components
Controlled Components將原來子元件改變state的邏輯移到父元件中,由父元件控制。一個運用Controlled Components最普遍的例子就是<input/>
,不傳任何props
的情況下React可以直接用預設的<input/>
元件,但是如果加了value
屬性,如果不傳onChange
屬性開始輸入的話, input框不會有任何變化, 因為<input/>
已經被父元件控制, 需要父元件指定一個方法, 告訴子元件value
如何變化.
class App extends Component{
state = {
value: '',
}
onChange = (e) => {
this.setState({value: e.target.value})
}
render() {
return (
<input value={this.state.value} onChange={this.onChange}/>
);
}
}
複製程式碼
下面是一個實際的例子, 如果給Counter
元件傳入count
屬性的話, Counter
元件自己的addCount
就不再起作用, 需要使用者傳入一個新的addCount
方法來決定count
如何變化.
class Counter extends Component{
state = {
count: 0
}
isControlled(prop) {
return this.props[prop] !== undefined
}
getState() {
return {
count: this.isControlled('count') ? this.props.count : this.state.count,
}
}
addCount = () => {
if (this.isControlled('count')) {
this.props.addCount()
} else {
this.setState(state => ({count: state.count + 1}))
}
}
render() {
return (
<div>
<p>You clicked {this.getState().count} times</p>
<button onClick={() => this.addCount()}>
Click me
</button>
</div>
);
}
}
class App extends Component{
state = {
count: 0,
}
addCount = () => {
this.setState(state => ({count: state.count + 2}))
}
render() {
return (
<Fragment>
{/*this counter add 1 every time*/}
<Counter/>
{/*this counter add 2 every time*/}
<Counter count={this.state.count} addCount={this.addCount}/>
</Fragment>
);
}
}
複製程式碼
Hook
Hook 是一些可以讓你在函式元件裡“鉤入” React state 及生命週期等特性的函式,使用者可以在不使用class
的情況下用一些 React 的特性,如state
等等.
useState
就是一個 Hook 。useState
唯一的引數就是初始 state,通過在函式元件裡呼叫它來給元件新增一些內部 state。React 會在重複渲染時保留這個 state。useState
會返回一對值:當前狀態和一個讓你更新它的函式,你可以在事件處理函式中或其他一些地方呼叫這個函式。它類似 class 元件的 this.setState
,但是它不會把新的 state 和舊的 state 進行合併。
你之前可能已經在 React 元件中執行過資料獲取、訂閱或者手動修改過 DOM。我們統一把這些操作稱為“副作用”,或者簡稱為“作用”。
useEffect
就是一個 Effect Hook,給函式元件增加了操作副作用的能力。它跟 class 元件中的 componentDidMount
、componentDidUpdate
和 componentWillUnmount
具有相同的用途,只不過被合併成了一個 API。
useContext
則可以傳入Context
物件,獲取當前的context value. 當相應的Context.Provider
更新value, useContext
會觸發rerender, 並傳入最新的context value
import React, { useState, useEffect, useContext } from 'react';
const TitleContext = React.createContext({})
const TitleProvider = TitleContext.Provider
function Counter() {
// Declare a new state variable, which we'll call "count"
const [count, setCount] = useState(0);
useEffect(() => {
// Update the document title using the browser API
console.log(`You clicked ${count} times`)
});
const contextTitle = useContext(TitleContext)
return (
<div>
<h1>{contextTitle}</h1>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
const App = props => (
<TitleProvider value={'Counter'}>
<Counter/>
</TitleProvider>
)
複製程式碼
以下是React所有的hook:
參考:
blog.logrocket.com/guide-to-re…
engineering.dollarshaveclub.com/reacts-stat…