REACT與函數式編程
從發布最初,react就高舉函數式編程的大旗。但最最核心的組件化確是用ES6的偽造類Class完成的。開發過程中明顯的感覺得到,這是面向對象的編程風格。現在好了,開發的趨勢是組件可以不用class完成,而是改用函數組件來編寫我們的應用了。
Counter組件重寫
套用程墨菊苣在知乎上的例子,以前我們的class類組件的counter,就可以改寫成下面的例子
const Counter = () => {
const [count,setCount] = useState(0);
return (
<div>
<div>{count}</div>
<button onClick={()=>setCount(count + 1)}>+</button>
<button onClick={()=>setCount(count - 1)}>-</button>
</div>
);
};
hooks的出現,讓redux 和 mobx 這樣的第三方狀態管理工具可有可無了
hooks的useState無論調用多少次,無論在哪個組件如何調用,互相之間都是獨立的。如何獨立的呢?react要求useState只能在react組件函數一級的scope里,不能放在if-else里,不能放在循環體里,因為react要根據useState的調用順序來管理狀態。
const [ foo,updateFoo] = useState("bar");
上面的代碼,useState返回的結果其實是一個倆元素的數據,第一個元素是狀態,相當于之前的this.state.foo,第二個元素是改變這個狀態的函數,相當于之前的this.setState(...)。
useState毫無「狀態名」的概念,狀態名和改變狀態的函數,都是我們自己的代碼確定的,上面的代碼完全可以這么寫。
const [ so,whatever] = useState("bar");
然后so變量就是bar了,whatever就是改變so的函數了。
因為useState沒有狀態名的概念,所以完全靠調用順序[記住]哪個狀態對應哪個變量。
可以想象,如下面這樣編寫代碼,肯定玩完。
const Counter = ()=>{
if(condition){
const [count,setCount] = userState(0);
const [foo,updateFoo] = useState("bar");
}else{
const [foo,updateFoo] = useState("bar");
const [count,setCount] = useState(0);
}
return(
<div>
<div>{count}</div>
<button onClikc={()=>setCount(count + 1)}>+</button>
<button onClikc={()=>setCount(count - 1)}>-</button>
</div>
);
};
上面兩個useState的調用順秀隨著condition變化,這么不行。
為什么引入hooks
為了解決react開發中出現的各種關于組件的問題
- 難以重用或共享組件中關于狀態(state)相關的邏輯.
- 邏輯復雜的組件難以開發和維護,當我們的組件需要處理多個復雜的不相關的local state的時候每個生命周期函數中可能會包含著各種互不相關的邏輯在里面。
- 類組件中的this增加了學習成本,類組件在基于現有工具的優化上存在諸多問題。
- 由于業務變動,函數組件不得不改為類組件等。
為了進一步了解hooks,我們先來了解一下hooks的用法。
快速了解react hooks的使用
hooks讓函數組件擁有了類組件的特性,比如local state(狀態),lifeCircle(生命周期),而且還解決了上面提到的一系列問題。下面來一一指出。
首先是hooks的使用,這里主要講2個hooks:useState和useEffect。
import {useState,useEffect} from "react";
function example(){
// count是名 setCount是一個函數 他們隨便定義成什么名字都可以
const [count,setCount] = useState(0);
//useEffect參數是一個函數
useEffect(()=>{
document.title = `You clicked ${count} times`;
});
return (
<p>{count}</p>
<button onClick={()=>setCount(count + 1)}>Click Me</button>
)
}
useState
useState這個方法可以給我們的函數組件帶來local state,讓函數組件擁有狀態的概念,它接收一個初始的state值,返回一對變量。
const [count,setCount] = useState(0);
//等價于
var count = useState(0)[0]; //該state
var setCount = useState(0)[1];//修改state的方法
useEffect
useEffect可以利用我們組件中的local state 進行一些帶有副作用的操作
//在useEffect里面可以獲取到local state
useEffect(()=>{
document.title = `You clicked ${count} times`;
});
useEffect里面還可以通過傳入第二個參數來決定是否執行里面的操作來避免一些不必要的性能損失,只要第二個參數數組中的成員的值沒有改變,就會跳過此次執行,如果傳入一個空數組,[],那么該effect只會在組件mount和unmount時期執行。
useEffect(()=>{
document.title = `You clicked ${count} times`;
},[count]);
useEffect中還可以通過讓函數返回一個函數來進行一些清理操作(clean up),比如取消訂閱等。
useEffect(() => {
api.subscribe(theId);
return () => {
api.unsubscribe(theId) //clean up
}
});
useEffect什么時候執行呢?它會在組件mount和unmount以及每次重新渲染的時候都會執行,也就是會在componentWillMount,componentDidMount,componentDidUpdata這3個時期執行。
清理函數(clean up)什么時候執行呢?其在前一次effect執行后,下一次effect將要執行前,以及Unmount時期執行。
注意事項
我們只能在 函數組件 中使用 Hooks,我們也可以在一個組件中使用多組 Hooks。比如:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() =>{
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
API.subscribe(props.friend.id);
return () => {
API.unsubscribe(props.friend.id);
};
});
return isOnline
}
值得注意的是,我們只能在頂層代碼(top level)中調用hooks,不能再循環或者判斷語句中調用,這樣是為了讓我們的hooks在每次渲染的時候都會按照相同的順序調用,因為這里有一個跟關鍵詞的問題,那就是useState需要依賴參照第一次渲染的調用順序來匹配對應的state,否則,useState會無法正確返回其對應的state。
hooks解決的問題。
知道了 Hooks 基本使用后,我們就可以來了解 Hooks 是怎么解決 react 長期存在的問題的。
- 如何解決 狀態有關的邏輯(stateful logic) 的重用和共享問題
過去對于類似問題的解決方案主要有兩個:
- Render Props 通過props接受一個返回react element的函數,來動態決定自己要渲染的結果;
<DataProvider render=>(
<h1>Hello,{data.target}</h1>
)}>
- 還有就是Higher-Order Components 高階組件,以一種類似 工廠模式 的方式去生產出具有相同或類似邏輯的組件。
function getComponent(WrappedComponent) {
return class extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {
// doSomething
}
componentWillUnmount() {
// doSomething
}
render() {
return <WrappedComponent {...this.props} />;
}
};
}
但無論是哪種方法都會造成組件數量增多,還有組件樹結構的更改,而且還有可能出現嵌套地獄。現在custom hooks可以解決這個問題。
custom hooks
custom hooks并不是一個api,而是一個規則。具體實現就是通過一個函數來封裝跟狀態有關的邏輯,將這些邏輯從組件中抽離出來,在這個函數中我們可以使用其他的hooks,也可以單獨進行測試,甚至將他貢獻給社區。
import { useState, useEffect } from 'react';
function useCount() {
const [count, setCount] = useState(0);
useEffect(() =>{
document.title = `You clicked ${count} times`;
});
return count
}
比如上面的一個例子,他就是一個custom hooks,提取了對count的操作。這里需要遵循一個約定,命名要用use *,這是為了方便我區分,利于我們維護。可以看到它其實就是一個函數,我們可以在現有的所有其他組件引用他。
function CountStatus(){
// 比如這里的useCount 就是用use命名。
const count = useCount();
return count;
}
這里的核心概念就是將邏輯提取出來封裝到custom hooks里,然后可以在任何其他組件中共享這部分邏輯。也可以貢獻給社區。所以我們可以預見,將來會有更多充滿想象力的custom hooks出現,極大地提高我們的開發效率。
hooks 具有復雜邏輯的組件的開發和維護
我們的組件可能會隨著開發的進行變得越來越復雜,要處理越來越多的 local State,那么在組件的生命周期函數中就會充斥著各種互不相關的邏輯,這里需要引入官方的比較復雜的例子,先看基于以前類組件的情況:
class FriendStatusWithCounter extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0,
isOnline: null
};
this.handleStatusChange = this.handleStatusChange.bind(this);
}
componentDidMount() {
document.title = `You clicked ${this.state.count} times`;
ChatAPI.subscribeToFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
componentDidUpdate() {
document.title = `You clicked ${this.state.count} times`;
}
componentWillUnmount() {
ChatAPI.unsubscribeFromFriendStatus(
this.props.friend.id,
this.handleStatusChange
);
}
handleStatusChange(status) {
this.setState({
isOnline: status.isOnline
});
}
// ...
經過 Hook 改造后:
function FriendStatusWithCounter(props) {
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
const [isOnline, setIsOnline] = useState(null);
useEffect(() => {
ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);
return () => {
ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
};
});
function handleStatusChange(status) {
setIsOnline(status.isOnline);
}
// ...
}
狀態和相關的處理邏輯可以按照功能來劃分,不必散落在各個生命周期之中,大大降低了開發和維護的難度。除了這幾個hooks還有其他幾個hooks,在此繼續了解 Hooks API Reference。