React16.8開始內置了10個Hook,核心是2個: 狀態管理:useState 副作用管理:useEffect 有狀態的函數 useState 有狀態組件寫法: class Example extends React.Component { constructor(props) { super ...
React16.8開始內置了10個Hook,核心是2個:
- 狀態管理:useState
- 副作用管理:useEffect
有狀態的函數
useState
有狀態組件寫法:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
}
render() {
return (
<div>
<p>You clicked {this.state.count} times</p>
<button onClick={() => this.setState({ count: this.state.count + 1 })}>
Click me
</button>
</div>
);
}
}
無狀態組件寫法:
const Example = props => {
const { count, onClick } = props;
return (
<div>
<p>You clicked {count} times</p>
<button onClick={onClick}>
Click me
</button>
</div>
)
}
hooks是有狀態的函數:
import { useState } from 'react';
const Example = () => {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
)
}
註意,useState生成的setter在更新state的時候不會合併:
const [data, setData] = useState({ count: 0, name: 'abc' }); // name沒有被使用,應該分開聲明
useEffect(() => {
// data: { count: 0, name: 'abc' } -> { count: 0 }
setData({ count: 1 })
})
在我們的純函數組件里,每個useState都會生產一對state和stateSetter,我們無需考慮更多的狀態樹的設計和組件的劃分設計,邏輯代碼可以直接從根組件寫起。
我們應用的發展途徑大致分為以下3個階段,基於hook開發過程將更有彈性:
- 前期farm:只需要把相關 state 組合到幾個獨立的 state 變數即可應付絕大多數情況
- 中期gank:當組件的狀態逐漸變得多起來時,我們可以很輕鬆地將狀態的更新交給reducer來管理
- 後期團戰:不光狀態多了,狀態的邏輯也越來越複雜的時候,我們可以幾乎0成本的將繁雜的狀態邏輯代碼抽離成自定義hook解決問題
高度靈活的redux,純粹無依賴
不同於真正的redux,在實際應用中,hook帶來了一種更加靈活和純粹的模式。現在我們可以用10行代碼實現一個全局的redux,也可以用2行代碼隨時隨地實現一個局部的redux:
10行代碼實現一個全局的redux:
import React from 'react';
const store = React.createContext(null);
export const initialState = { name: 'aa' };
export function reducer(state, action) {
switch (action.type) {
case 'changeName': return { ...state, name: action.payload };
default: throw new Error('Unexpected action');
}
}
export default store;
Provider根組件掛上:
import React, { useReducer } from 'react';
import store, { reducer, initialState } from './store';
function App() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<store.Provider value={{ state, dispatch }}>
<div />
</store>
)
}
子組件調用:
import React, { useContext } from 'react';
import store from './store';
function Child() {
const { state, dispatch } = useContext(store);
}
隨時隨地的一個局部redux:
import React, { useReducer } from 'react';
const initialState = { name: 'aa' };
function reducer(state, action) {
switch (action.type) {
case 'changeName': return { ...state, name: action.payload };
default: throw new Error('Unexpected action');
}
}
function Component() {
const [state, dispatch] = useReducer(reducer, initialState);
...
}
自定義hook
當我們想在兩個函數之間共用邏輯時,我們會把它提取到第三個函數中。而組件和 hook 都是函數,所以也同樣適用這種方式。不同的是,hook 是有狀態的函數,它能實現以往純函數所不能做到的更高級別的復用——狀態邏輯的復用。
component寫法:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
name: undefined
};
}
componentDidMount() {
service.getInitialCount().then(data => {
this.setState({ count: data });
});
service.getInitialName().then(data => {
this.setState({ name: data });
});
}
componentWillUnmount() {
service.finishCounting().then(() => {
alert('計數完成');
});
}
addCount = () => {
this.setState({ count: this.state.count + 1 });
};
handleNameChange = name => {
this.setState({ name });
};
render() {
const { count, name } = this.state;
return (
<div>
<div>
<p>You clicked {count} times</p>
<button onClick={this.addCount}>Click me</button>
</div>
<Input value={name} onChange={this.handleNameChange} />
</div>
);
}
}
hook寫法:
function useCount(initialValue) {
const [count, setCount] = useState(initialValue);
useEffect(() => {
service.getInitialCount().then(data => {
setCount(data);
});
return () => {
service.finishCounting().then(() => {
alert('計數完成');
});
};
}, []);
function addCount() {
setCount(c => c + 1);
}
return { count, addCount };
}
function useName(initialValue) {
const [name, setName] = useState(initialValue);
useEffect(() => {
service.getInitialName().then(data => {
setName(data);
});
}, []);
function handleNameChange(value) {
setName(value);
}
return { name, handleNameChange };
}
const App = () => {
const { count, addCount } = useCount(0);
const { name, setName } = useName();
return (
<div>
<p>You clicked {count} times</p>
<button onClick={addCount}>Click me</button>
<Input value={name} onChange={setName} />
</div>
);
};
如上,使用component的寫法里,count和name,還有與之相關的一票兒邏輯,散落在組件的生命周期和方法里。雖然我們可以將組件的state和變更action抽成公共的,但涉及到副作用的action,到最終還是繞不開組件的生命周期。但一個組件的生命周期只有一套,不可避免的會出現一些完全不相干的邏輯寫在一起。如此一來,便無法實現完全的狀態邏輯復用。
我們再看看使用hook的寫法,我們將count相關的邏輯和name相關的邏輯通過自定義hook,封裝在獨立且封閉的邏輯單元里。以往class組件的生命周期在這裡不復存在。生命周期是和UI強耦合的一個概念,雖然易於理解,但它天然距離數據很遙遠。而hook以一種類似rxjs模式的數據流訂閱實現了組件的副作用封裝,這樣的好處就是我們只需要關心數據。所以hook所帶來的,絕不僅僅只是簡化了state的定義與包裝。
自定義hook實現了狀態邏輯與UI分離,通過合理抽象自定義hook,能夠實現非常高級別的業務邏輯抽象復用
hook原理
let hooks, i;
function useState() {
i++;
if (hooks[i]) {
// 再次渲染時
return hooks[i];
}
// 第一次渲染
hooks.push(...);
}
// 準備渲染
i = -1;
hooks = fiber.hooks || [];
// 調用組件
Component();
// 緩存 Hooks 的狀態
fiber.hooks = hooks;
本文由博客一文多發平臺 OpenWrite 發佈!