ReactHook快速上車

React16.8開始內置了10個Hook,核心是2個:react

  • 狀態管理:useState
  • 反作用管理:useEffect

有狀態的函數

useStateredux

有狀態組件寫法:數組

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是有狀態的函數:ide

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,咱們無需考慮更多的狀態樹的設計和組件的劃分設計,邏輯代碼能夠直接從根組件寫起。this

咱們應用的發展途徑大體分爲如下3個階段,基於hook開發過程將更有彈性:spa

  • 前期farm:只須要把相關 state 組合到幾個獨立的 state 變量便可應付絕大多數狀況
  • 中期gank:當組件的狀態逐漸變得多起來時,咱們能夠很輕鬆地將狀態的更新交給reducer來管理
  • 後期團戰:不光狀態多了,狀態的邏輯也愈來愈複雜的時候,咱們能夠幾乎0成本的將繁雜的狀態邏輯代碼抽離成自定義hook解決問題

高度靈活的redux,純粹無依賴

不一樣於真正的redux,在實際應用中,hook帶來了一種更加靈活和純粹的模式。如今咱們能夠用10行代碼實現一個全局的redux,也能夠用2行代碼隨時隨地實現一個局部的redux:設計

10行代碼實現一個全局的redux:code

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 發佈!

相關文章
相關標籤/搜索