React Hooks

React

使用 React Hooks 的无状态组件,在每次渲染时,包括组件内的函数,都会保留所有 state ,props 在执行时刻的值,这一点和 class 版本的组件具有很大的差异,class 版本会取到最新值。

import React, { Component } from 'react';
class OldApp extends Component {


    constructor(props) {
        super(props);
        this.state = {
            count: 1
        }

    }

    testOnclick = () => {
        setTimeout(() => {
            console.log(this.state.count);
        }, 3000);
    }

    render() {
        return (
            <div>
                <div>
                    oldApp
                </div>
                <div>{this.state.count}</div>
                <button onClick={() => { this.setState({ count: this.state.count+1 }) }} >add</button>
                <button onClick={() => { this.testOnclick(); }} >show</button>
            </div>
        )
    }
}

export default OldApp;

多次点击 add ,交错点击 show,console 里输出最新状态的 count n 次。

function App() {
  const [count, setCount] = useState({ name: 1 });

  function handleAlertClick() {
    setTimeout(() => {
      alert(count.name);

    }, 3000);
  }

  return (
    <div className="App">
      <OldApp />
      <p>{count.name}</p>
      <button onClick={() => { setCount({ name: count.name + 1 }) }} > add</button>
      <button onClick={() => { handleAlertClick() }} >alert</button>
    </div>
  );
}

export default App;

多次点击 add ,交错点击 alert ,console 多次出现被调用当时的值。

class 版本可以使用闭包修复,实际 Hooks 依赖 JavaScript 闭包。如果希望无状态组件获取到最新值,想 class 这样的表现,可以使用useRef

function App() {
  const [count, setCount] = useState({ num: 1 });
  const lastCount=useRef(count);
  function handleAlertClick() {
    lastCount.current = count;
    setTimeout(() => {
      console.log(lastCount.current.num);
    }, 3000);
  }

  return (
    <div className="App">
      <p>{count.num}</p>
      <button onClick={() => { setCount({ num: count.num + 1 }) }} > add</button>
      <button onClick={() => { handleAlertClick() }} >alert</button>
    </div>
  );
}

React Hooks

  • useMemo:只有在某个依赖项改变时才会重新计算

    const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
    
  • useCallback:把内联函数以及依赖数组作为参数传入,将返回回调函数的 memoized 版本。

    const memoizedCallback = useCallback(
      () => {
        doSomething(a, b);
      },
      [a, b],
    );
    

    直接定义的函数,在每次渲染时其实都会变化,这样的函数无法作为其他 Hook 的依赖存在,通过 useCallback 定义的函数,可作为其他 hooks 的依赖存在。当有多个 useEffect ,我们希望抽象出 useEffect 相同部分的逻辑,这部分逻辑依赖于 props 或者 state 时,可以考虑使用 useCallback。

  • useContext:上下文

    import React from 'react';
    export default React.createContext(null);
    
    import React, { useEffect, useState, useRef, useReducer, useContext } from 'react';
    import './App.css';
    import Test from './Test';
    import ToDoContext from './ToDoContext';
    
    
    function App() {
      const [count, dispatch] = useReducer(reducer, { num: 1 })
    
      function reducer(state, action) {
        switch (action.type) {
          case 'add':
            return {
              ...state,
              num: state.num + 1
            }
        }
      }
    
      return (
        <ToDoContext.Provider value={{count,dispatch}} >
          <Test></Test>
        </ToDoContext.Provider>
      );
    }
    export default App;
    
    import React, { useContext } from 'react';
    import ToDoContext from './ToDoContext';
    
    function Test() {
        const { count, dispatch } = useContext(ToDoContext);
        return (
            <div>{count.num}</div>
        )
    }
    export default Test;
    

    上下文用于解决 React 层层传递数据的问题,被包裹的子组件可以获取到全局数据,通常全局状态树会使用到上下文。

  • useEffect:在 render 之后执行的的方法,可以理解为 componentDidMount 或者 componentDidUpdate生命周期经常完成的操作,但是不一样的是,我们可以通过传递依赖的形式,确保代码仅在依赖变化时执行,这点我们之前使用shouldComponentUpdate进行props的对比类似。

  • useRef:使用 useRef 创建的对象和之间创建的对象的不同之处在于,useRef 返回的对象,在每次使用时会拿到最新值,而不是当次渲染值。

  • useReducer:当改变状态的逻辑很复杂时,我们通常使用 useReducer 来实现,而其他地方只需要 dispatch 相应的 type 不需要关心如何改变。同时结合useContext 我们可以做到统一的状态树管理

useEffect 详解

useEffect 用来处理会有副作用的操作,比如之前我们在生命周期函数中常常使用的获取数据操作。

 useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
 
    setData(result.data);
  });

但是这样并不理想,因为不经在组件加载时会执行,在组件更新时也会执行,因此,对于只需要在加载阶段执行的操作,我们通常给予一个空依赖。

  useEffect(async () => {
    const result = await axios(
      'https://hn.algolia.com/api/v1/search?query=redux',
    );
 
    setData(result.data);
  }, []);

如此在第一次执行之后,useEffect 不会再执行,因为依赖未变化(为空)。

但是这样依然不完美,useEffect 并不希望函数有返回,而异步函数实际上会返回一个AsyncFunction ,会报警告,因此我们可以这样优化。

useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        'https://hn.algolia.com/api/v1/search?query=redux',
      );
 
      setData(result.data);
    };
 
    fetchData();
  }, []);

以上操作实际使用 effects 模拟了传统的生命周期函数ComponentDidMount。而实际上我们应该用不同的眼光来看待useEffect

例如常见的查询获取数据操作

  const [search, setSearch] = useState('redux');
 
  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${search}`,
      );
 
      setData(result.data);
    };
 
    fetchData();
  }, [search]);
 

页面操作改变 search ,依赖于 search 的 effect 重新执行。

错误处理

 const [isError, setIsError] = useState(false);
 
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
 
      try {
        const result = await axios(url);
 
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
 
      setIsLoading(false);
    };
 
    fetchData();
  }, [url]);

使用一个 state 存放错误,并显示在页面

自定义 Hook

我们将获取数据,错误判断,加载等从 APP 组件抽离,形成一个自定义的 Hooks。

const useHackerNewsApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
 
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
 
      try {
        const result = await axios(url);
 
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
 
      setIsLoading(false);
    };
 
    fetchData();
  }, [url]);
 
  return [{ data, isLoading, isError }, setUrl];
}

在 App 中使用

function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useHackerNewsApi();
 
  return (
    <Fragment>
      <form onSubmit={event => {
        doFetch(`http://hn.algolia.com/api/v1/search?query=${query}`);
        event.preventDefault();
      }}>
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
 
      ...
    </Fragment>
  );
}

同样可以抽离初始值

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';
 
const useDataApi = (initialUrl, initialData) => {
  const [data, setData] = useState(initialData);
  const [url, setUrl] = useState(initialUrl);
  const [isLoading, setIsLoading] = useState(false);
  const [isError, setIsError] = useState(false);
 
  useEffect(() => {
    const fetchData = async () => {
      setIsError(false);
      setIsLoading(true);
 
      try {
        const result = await axios(url);
 
        setData(result.data);
      } catch (error) {
        setIsError(true);
      }
 
      setIsLoading(false);
    };
 
    fetchData();
  }, [url]);
 
  return [{ data, isLoading, isError }, setUrl];
};
 
function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useDataApi(
    'https://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
  );
 
  return (
    <Fragment>
      <form
        onSubmit={event => {
          doFetch(
            `http://hn.algolia.com/api/v1/search?query=${query}`,
          );
 
          event.preventDefault();
        }}
      >
        <input
          type="text"
          value={query}
          onChange={event => setQuery(event.target.value)}
        />
        <button type="submit">Search</button>
      </form>
 
      {isError && <div>Something went wrong ...</div>}
 
      {isLoading ? (
        <div>Loading ...</div>
      ) : (
        <ul>
          {data.hits.map(item => (
            <li key={item.objectID}>
              <a href={item.url}>{item.title}</a>
            </li>
          ))}
        </ul>
      )}
    </Fragment>
  );
}
 
export default App;

posted @ 2020-06-28 15:29  我不是陈懋平  阅读(150)  评论(0)    收藏  举报