一. why hooks:
在React conference 2018上, React團隊指出了React社區中被廣泛關注的問題:
(1) 邏輯複用問題: react中,組件是構建應用的基本組成部分, 爲了組件更好地複用,目前存在兩種方案: 高階組件(HOC) 和Render props,這些方案都有不同的適用場景,但是也帶來了明顯的缺點,如組件地獄
(2)巨型組件(Giant components):組件中各種的生命週期函數,但是組件的邏輯是相似的,如componentDidMount和ComponentDidUpdate的時候都可能存在發送ajax請求 或者是相反的, 如componentDidMount和componentWillUnMount會添加/清除綁定的事件, 計時器等
(3)class component還是functional component
- functional component簡單, 但是無法確保以後會不會添加其他的生命週期函數, 造成組件重寫,
- class component要寫的代碼多,不夠簡練, 還要面臨 this這類煩人的問題; 難以進編譯器行優化, 不利於熱加載
1. [state, useState]
class Greeting extends React.Component {
constructor(props) {
super(props);
this.state = {
name: 'Allen'
}
this.handleNameChange = this.handleNameChange.bind(this);
}
handleNameChange(e) {
this.setState({
name: e.target.value
})
}
render() {
return (
<Row>
<input
value={this.state.name}
onChange={this.handleNameChange}
/>
</Row>
)
}
}
ReactDOM.render(
<Greeting />,
document.querySelector('#root')
)
將class component換成 functional component是這樣的, 是不是簡潔了好多!
function Greeting(props) {
const [name, setName] = React.useState('Marry')
function handleNameChange(e) {
setName(e.target.value);
}
return (
<Row>
<input
value={name}
onChange={handleNameChange}
/>
</Row>
)
}
注意這裏的
const [name, setName] = React.useState('Marry'); // resource爲值, setResource爲設置值的函數, []裏面的命名可以任意
2. useContext
const locale = React.createContext('English');
<LocaleContext.Consumer>
{LocaleContext => (
<Row label="Language">
{LocaleContext}
</Row>
)}
</ LocaleContext.Consumer>
使用useContext進行替換
let LocalContext = React.createContext('English')
const locale = React.useContext(LocaleContext);
<Row label="language">
{locale}
</Row>
3.useEffect
假設現在需要體添加一個myName的div來實時顯示當前的名稱, 我們可以使用componentDidMount和componentDidUpdate的方法
this.state = {
name: 'Allen',
myName: '',
}
componentDidMount() {
this.state.myName = this.state.name;
}
componentDidUpdate() {
this.state.myName = this.state.name;
}
<Row label="The name is :">
<div className="name">{this.state.myName}</div>
</Row>
使用useEffect方法
const [myName, setMyName] = React.useState('');
React.useEffect(() => {
setMyName(name);
})
看完上面的api你可能會認爲, hooks就是爲了讓functional component具有state和lifecycle method, 實際上並不完全是,hooks的更大用處在於代碼的複用
二. hooks是如何進行代碼複用的
假設我們有這樣一個useEffect的邏輯, 接受一個resource參數, 返回一個resource array, 我們提取出一個useResource函數
import { useState, useEffect } from 'react';
import axios from 'axios';
const useResources = resource => {
const [resources, setResources] = useState([]);
useEffect(
() => {
(async resource => {
const response = await axios.get(
`https://jsonplaceholder.typicode.com/${resource}`
);
setResources(response.data);
})(resource);
},
[resource]
);
return resources;
};
export default useResources;
引用
import React from 'react';
import useResources from './useResources';
const UserList = () => {
const users = useResources('users');
return (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
export default UserList;
另外一個代碼複用的例子:
class App extends React.Component {
state = { lat: null, errorMessage: '' };
componentDidMount() {
window.navigator.geolocation.getCurrentPosition(
position => this.setState({ lat: position.coords.latitude }),
err => this.setState({ errorMessage: err.message })
);
}
renderContent() {
if (this.state.errorMessage && !this.state.lat) {
return <div>Error: {this.state.errorMessage}</div>;
}
if (!this.state.errorMessage && this.state.lat) {
return <SeasonDisplay lat={this.state.lat} />;
}
return <Spinner message="Please accept location request" />;
}
render() {
return <div className="border red">{this.renderContent()}</div>;
}
}
獲取地理位置的函數可以單獨抽取出來useLocation.js
import { useState, useEffect } from 'react';
export default () => {
const [lat, setLat] = useState(null);
const [errorMessage, setErrorMessage] = useState('');
useEffect(() => {
window.navigator.geolocation.getCurrentPosition(
position => setLat(position.coords.latitude),
err => setErrorMessage(err.message)
);
}, []);
return [lat, errorMessage]; //注意這裏的返回值
};
在App.js中
import useLocation from './useLocation';
const App = () => {
const [lat, errorMessage] = useLocation() // 一行代碼
const renderContent = () => {
if (errorMessage && !lat) {
return <div>Error: {errorMessage}</div>;
};
if (!errorMessage && lat) {
return <SeasonDisplay lat={lat} />;
};
return <Spinner message="Please accept location request" />;
}
return <div className="border red">{renderContent()}</div>;
}
``