1. 程式人生 > >React hooks詳解

React hooks詳解

此篇文章僅是對hooks入門的總結,老鳥略過吧~

React從16.8.X以後增加了一個新特性,react hooks 讓我們看看這個新特性又帶來了哪些驚喜呢~以下內容我們採取不同方式建立元件來進行對比總結

元件的建立方式:

用過react的都瞭解,傳統react建立元件提供了兩種方式,函式式與類(class)

class建立無狀態元件

class App extends React.Component {
  constructor(props) {
    super(props);
  }

  render() {
    return <div>
      <p>{this.props.name}</p>
    </div>
  }
}

function renderApp() {
  let appProps = {
    name: 'dqhan'
  }
  ReactDOM.render(
    <App  {...appProps} />,
    document.getElementById('app')
  )
}

renderApp();

新增狀態管理

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      name: props.name
    };
    this.handleChangeName = this.handleChangeName.bind(this);
  }

  handleChangeName() {
    this.setState({
      name: '我變了'
    })
  }

  render() {
    return (
      <React.Fragment>
        <p> {`hello~${this.state.name}`} </p>
        <button onClick={this.handleChangeName}></button>
      </React.Fragment>
    );
  }
}

我們通過class可以實現無狀態元件以及常規元件通過setState的狀態管理。

函式式建立元件

function App(props) {
  return <p>{`hello! ${props.name}`}</p>
}

function renderApp() {
  let appProps = { name: "dqhan" };
  ReactDOM.render(<App {...appProps} />, document.getElementById("app"));
}

函式式建立元件通常是無狀態元件,這種方式沒有辦法在內部對狀態統一管理,如果我們非要新增狀態管理呢,那就只能藉助redux啦~或者我們自己利用觀察者模式實現一個釋出訂閱(emmmm比較勉強吧,畢竟實際開發中我們不可能這麼做)

那麼如果我們非要這麼做呢,正題來了,React版本在16.8.X以後增添了一個新特性就是hooks。

hooks涉及API有useState、 useEffect、 useCallback、 useRef、 useMemo、 React.memo、 useReducer等,具體可以參考官方文件,我們來看一下hooks怎麼用

React Hooks建立元件

無狀態元件

function App(props) {
  return <div>
    <p>{`hello~${props.name}`}</p>
  </div>
}

哈哈,跟函式式一樣,畢竟我們要在函式式元件裡新增狀態管理嘛

1.useState

作用:新增狀態管理

function App() {
  let [name, setName] = useState('dqhan');
  return <div>
    <p>{`hello~${name}`}</p>
    <button onClick={() => setName('我變了')}>Click</button>
  </div>;
}

react hook可以管理自己的狀態,有自己的函式鉤子,這點相比要函式式顯然效果更好,不需要藉助redux,這就是我們為啥要在前面提到函數語言程式設計涉及狀態管理問題,就是要在這裡跟react hook做個比較。

到這裡我們知道了這個useState,多了一個useState讓函式式建立類有了自己的持久狀態。那麼在函式式裡面我們如何做到class元件中的setState呢?

function App(props) {
  let [name, setName] = useState('dqhan');
  let handleChangeName = useCallback(() => {
    setName(preState => {
      let updatedValues = {
        newValue: '我變了'
      }
      return { ...{ preState }, ...updatedValues }
    })
  })
  function click1(params) {
    setName('我變了1')
  }
  return <div>
    <p>{`hello~${name}`}</p>
    <button onClick={handleChangeName}>Click1</button>
    <button onClick={click1}>Click2</button>
  </div>;
}

這中方式已經實現了狀態整合,但是我們如果模擬一個state呢,來統一管理state呢,我們可以這麼實現

function App(props) {
  let [state, setState] = useState({
    name: 'dqhan'
  });
  let handleChangeName = useCallback(() => {
    setState(preState => {
      let updatedValues = {
        name: '我變了'
      }
      return { ...preState, ...updatedValues }
    })
  })
  return <div>
    <p>{`hello~${state.name}`}</p>
    <button onClick={handleChangeName}>Click</button>
  </div>;
}

到目前為止,已經知道了react hook中如何使用state,那麼周期函式呢,那麼就涉及另一個鉤子useEffect

2.useEffect

作用:周期函式

useEffect涉及三個周期函式 componentDidMount 、componentDidUpdate、 compinentWillUmount 我們看一個簡單的例項

function App() {
  var [count, setCount] = useState(0);
  useEffect(() => {
    console.log(`update--${count}`);
  }, [count]);
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Click</button>
    </div>
  );
}
下面我們來了解一下react hooks的周期函式,他是如何工作的
function App() {
  let [count, setCount] = useState(0);
  useEffect(
    () => {
      //預設每一次渲染都需要執行的方法
      console.log('didmount')
      //如果需要實現componentWillComponent,則return 一個函式即可    
      return function unmount() {
        console.log('unmount')
      }
    }
  )

  let handleSetCount = useCallback(() => {
    setCount((preCount) => {
      let updatedCount = preCount + 1;
      return updatedCount;
    });
  })

  return <React.Fragment>
    {console.log('render')}
    <p>{`${count}`}</p>
    <button onClick={handleSetCount}>Click</button>
  </React.Fragment>
}

我們可以看一下執行週期

第一次渲染時候執行 render  didmount

點選事件執行順序 render unmount didmount

不難看出,每一次渲染我們都會執行render進行渲染,然後清除掉上一次的useEffect,然後渲染完成之後重新執行useEffect

這樣通過一個useEffec可以預設執行兩個周期函式,也就是當我們需要對元件新增一些需要當元件解除安裝時候清除掉的功能時候,這個是很方便的,常見的就是setTimeout setIntrval等定時器

但是比如一個component渲染之後我們通常會發送一個請求來請求資料,然後重寫渲染這個元件,這樣會造成死迴圈怎麼辦,我們可以在useEffect後新增第二個引數

阻止useEffect每一次都要執行

function App() {
  let [count, setCount] = useState(0);
  useEffect(
    () => {
      //預設每一次渲染都需要執行的方法
      console.log('didmount')
      //如果需要實現componentWillComponent,則return 一個函式即可    
      return function unmount() {
        console.log('unmount')
      }
    },
    [setCount]
  )

  let handleSetCount = useCallback(() => {
    setCount((preCount) => {
      let updatedCount = preCount + 1;
      return updatedCount;
    });
  })

  return <React.Fragment>
    {console.log('render')}
    <p>{`${count}`}</p>
    <button onClick={handleSetCount}>Click</button>
  </React.Fragment>
}

當傳入第二個引數得值不變得時候就會跳過useEffect函式執行

如何模擬componentDidMount與componentWillUmount,第二個引數我們傳一個空陣列,這樣就可以實現僅當元件渲染跟元件解除安裝得時候執行

function App() {
  let [count, setCount] = useState(0);
  useEffect(
    () => {
      //預設每一次渲染都需要執行的方法
      console.log('didmount')
      //如果需要實現componentWillComponent,則return 一個函式即可    
      return function unmount() {
        console.log('unmount')
      }
    },
    []
  )

  let handleSetCount = useCallback(() => {
    setCount((preCount) => {
      let updatedCount = preCount + 1;
      return updatedCount;
    });
  })

  return <React.Fragment>
    {console.log('render')}
    <p>{`${count}`}</p>
    <button onClick={handleSetCount}>Click</button>
  </React.Fragment>
}

不過,這隱藏了一個問題:傳遞空陣列容易出現問題。如果咱們添加了依賴項,那麼很容易忘記向其中新增項,如果錯過了一個依賴項,那麼該值將在下一次執行useEffect時失效,並且可能會導致一些奇怪的問題。

常見得就是當我們想用父元件呼叫子元件時候使用得ref,或者我們要獲取dom焦點時

function App() {
  let [count, setCount] = useState(0);
  let [value, setValue] = useState('')
  const inputRef = useRef();

  useEffect(
    () => {
      //預設每一次渲染都需要執行的方法
      console.log('didmount')

      //如果需要實現componentWillComponent,則return 一個函式即可    
      return function unmount() {
        console.log('unmount')
      }
    },
    [inputRef]
  )

  let handleSetCount = useCallback(() => {
    setCount((preCount) => {
      let updatedCount = preCount + 1;
      return updatedCount;
    });
  })

  let handleSetValue = function (e) {
    setValue(e.target.value);
  }

  return <React.Fragment>
    {console.log('render')}
    <p>{`${count}`}</p>
    <input
      ref={inputRef}
      value={value}
      onChange={handleSetValue}
    ></input>
    <button
      ref={inputRef}
      onClick={handleSetCount}
    >Click</button>

  </React.Fragment>
}

下面我們實現一個請求例項

請求例項

function App() {
  let [data, setData] = useState(null);
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(config);
      setData(result);
    };

    fetchData();
  }, []);

  return <div></div>;
}

利用hook可以做到分離介面

function useFetchHook(config, watch) {
  let [data, setData] = useState(null);
  let [status, setStatus] = useState(0);
  useEffect(
    () => {
      const fetchData = async () => {
        try {
          const result = await axios(config);
          setData(result);
          setStatus(0);
        } catch (e) {
          setStatus(1);
        }
        fetchData();
      };
    },
    watch ? [...watch] : []
  );

  return { data, status };
}

現在我們整體上知道了useState useEffect怎麼使用了,我們來自己實現一個簡易的

實現useState

var val;
function useState(initVal) {
  let resultVal = val || initVal;
  function setVal(newVal) {
    resultVal = newVal;
    render();
  }
  return [resultVal, setVal]
}

實現useEffect

var watchArr = [];
function useEffect(fn, watch) {
  var hasWatchChange = true;
  hasWatchChange = watchArr && watch.every((val, i) => val === watchArr[i])
  if (hasWatchChange) {
    fn();
    watchArr = watch;
  }
}

hooks裡面最常用的兩個API就是useState與useEffect,現在是不是已經瞭解了呢,下面我們介紹一些其他API

3.useContext

作用:越級別獲取元件內容

類元件中我們也常用context,類元件實現方式


 const AppContext = React.createContext('target');
class App extends React.Component {
  constructor(props) {
    super(props)
  }
  render() {
    return (
      <AppContext.Provider value="dark">
        <Target />
      </AppContext.Provider>
    );
  }
}

class Target extends React.Component {
  //通過定義靜態屬性 contextType 來訂閱
  //沒有定義是獲取不到的
  static contextType = AppContext;
  render() {
    console.log(this.context);
    return <div></div>;
  }
}

Hooks實現方式

const AppContext = React.createContext('target');

function App() {
  useEffect(
    () => { },
    []
  );
  return <AppContext.Provider value="dark">
    <Target />
  </AppContext.Provider>;
}

function Target() {
  const value = useContext(AppContext);
  console.log(value);
  return <div></div>;
}

在需要訂閱多個 context 的時候,就更能體現出useContext的優勢。

傳統的實現方式

function App() {
  return <CurrentUser.Consumer>
    {
      user => <Notifications.Consumer>
        {notifications =>
          <header>
            Welcome back, {user.name}!
              You have {notifications.length} notifications.
          </header>
        }
      </Notifications.Consumer>
    }
  </CurrentUser.Consumer>
}

hooks實現

function App() {
  const user = useContext(CurrentUser);
  const notifications = useContext(Notifications);

  return (
    <header>
      Welcome back, {user.name}!
      You have {notifications.length} notifications.
    </header>
  );
}

是不是比傳統的要簡單的多

4.useReducer

作用:複雜狀態管理,跟redux本質上是一樣的

函式式元件如果涉及到狀態管理,我們需要藉助redux,那麼hooks需要嗎,答案也是一樣的,簡單的狀態管理我們可以通過useState來進行管理,如果比較複雜的狀態管理呢,react hook給我們提供了方法 useReducer

function init(initialCount) {
  return { count: initialCount };
}

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    case 'reset':
      return init(action.payload);
    default:
      throw new Error();
  }
}

function Counter({ initialCount }) {
  const [state, dispatch] = useReducer(reducer, initialCount, init);
  return (
    <>
      Count: {state.count}
      <button
        onClick={() => dispatch({ type: 'reset', payload: initialCount })}>
        Reset
      </button>
      <button onClick={() => dispatch({ type: 'increment' })}>+</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>-</button>
    </>
  );
}

5.useCallback

作用:提升效能,快取事件,減少沒必要的渲染

當我們使用類元件建立時,我們會怎麼繫結事件呢

class App extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    return <div>
      <p>{`hello~${name}`}</p>
      <button onClick={() => { console.log('click') }}>Click</button>
    </div>
  }
}

這樣寫會導致什麼結果呢,就是當渲染的時候react會認為每一次繫結的事件都是新的,從而從新進行計算

改進如下

class App extends React.Component {
  constructor(props) {
    super(props);
    this.handleClick = this.handleClick.bind(this);
  }
  handleClick() {
    console.log('click')
  }
  render() {
    return <div>
      <p>{`hello~${name}`}</p>
      <button onClick={this.handleClick}>Click</button>
    </div>
  }
}

我們講觸發函式繫結在this上,來快取這個方法

hooks

function App() {
  let [count, setCount] = useState(0);
  return <div>
    <button onClick={() => setCount(1)} ></button>
  </div>
}

同樣的問題這麼寫也是存在的,改進如下

function App() {
  let [count, setCount] = useState(0);
  let handleSetCount = useCallback(() => {
    setCount(1);
  })
  return <div>
    <button onClick={handleSetCount} ></button>
  </div>
}

我們通過useCallback來快取這個事件達到優化效果

6.useMemo

作用:提升效能,選擇性的渲染變化元件

function App(target, target2) {
  const target = useMemo(() => {
    return <Target />
  }, [target])
  const target2 = useMemo(() => {
    return <Target2 />
  }, [target2])
  return <div>
    {target}
    {target2}
  </div>
}

當target變化僅渲染Target元件,同理也作用與Target2元件

React.memo

作用:提升效能

如果想實現class中的shouldComponentUpdate方法呢 ,區別是它只能比較 props,不會比較 state:

const App = React.mome((target, target2) => {
  const target = useMemo(() => {
    return <Target />
  }, [target])
  const target2 = useMemo(() => {
    return <Target2 />
  }, [target2])
  return <div>
    {target}
    {target2}
  </div>
})

7.useRef

作用:獲取dom依賴關係

類元件實現方式

class App extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }

  componentDidMount() {
    this.myRef.current.focus();
  }

  render() {
    return <input ref={this.myRef} type="text" />;
  }
}

hooks

function App() {
  let [value, setValue] = useState('')
  const inputRef = useRef();

  useEffect(
    () => {
    },
    [inputRef]
  )

  let handleSetValue = function (e) {
    setValue(e.target.value);
  }

  return <React.Fragment>
    <input
      ref={inputRef}
      value={value}
      onChange={handleSetValue}
    ></input>

  </React.Fragment>
}

好了hooks的基本使用方式就介紹完了,現在你對hook多少能瞭解了一些吧~

程式碼地址:https://github.com/Dqhan/React

   

 

 

&n