jtwang7 / React-Note

React 学习笔记
8 stars 2 forks source link

React - 数据请求 #11

Open jtwang7 opened 3 years ago

jtwang7 commented 3 years ago

How to fetch data in React

参考文章: How to fetch data in React

Where to fetch data in React's component tree?

请求数据的位置 = 所有用到该数据的组件的公共父组件内

How to fetch data in React

  1. 为什么将请求结果存储在 state 中?
    • 请求是异步的,因此需要在请求成功后将结果保存。
    • componentDidMount 执行在 render 之后,我们获取请求结果后,需要重新渲染组件以展示效果,因此需要借助 setState 触发重渲染。

      When this method runs, the component was already rendered once with the render() method, but it would render again when the fetched data would be stored in the local state of the component with setState(). Afterward, the local state could be used in the render() method to display it or to pass it down as props.

  2. 请求数据前 render,请求数据后又一次 render,会不会发生页面闪烁? 不需要担心该问题,数据请求后,React 仅会重构那些与 state 相关的部分,不会刷新整个页面。

axios with promise

import React, { Component } from 'react';
import axios from 'axios';

const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';

class App extends Component {
  constructor(props) {
    super(props);

    this.state = {
      hits: [],
      isLoading: false,
      error: null,
    };
  }

  componentDidMount() {
    this.setState({ isLoading: true });

    axios.get(API + DEFAULT_QUERY)
      .then(result => this.setState({
        hits: result.data.hits,
        isLoading: false
      }))
      .catch(error => this.setState({
        error,
        isLoading: false
      }));
  }

  ...
}

export default App;

axios with async/await

import React, { Component } from 'react';
import axios from 'axios';

const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';

class App extends Component {
  ...
  // 使用 await 时,最外层必须声明为 async 函数
  async componentDidMount() {
    this.setState({ isLoading: true });
    // async/await 实现了以同步方式书写异步代码
    // 但 async/await 没有像 promise 一样内置的错误捕获机制,可以使用 try / catch。
    try {
      const result = await axios.get(API + DEFAULT_QUERY);

      this.setState({
        hits: result.data.hits,
        isLoading: false
      });
    } catch (error) {
      this.setState({
        error,
        isLoading: false
      });
    }
  }

  ...
}

export default App;

How to fetch data in HIGHER-ORDER COMPONENTS?

我们可以将数据请求逻辑提升到高阶组件中,以实现逻辑复用。

// 利用了高阶函数的思想,实现额外参数的提取(此处为 url),返回高阶组件。
// 高阶组件接收一个组件,并最终返回一个组件。
const withFetching = (url) => (Component) =>
  class WithFetching extends React.Component {
    constructor(props) {
      super(props);
      // 存储请求结果
      this.state = {
        data: null,
        isLoading: false,
        error: null,
      };
    }
    // 数据请求公共逻辑
    componentDidMount() {
      this.setState({ isLoading: true });

      axios.get(url)
        .then(result => this.setState({
          data: result.data,
          isLoading: false
        }))
        .catch(error => this.setState({
          error,
          isLoading: false
        }));
    }

    render() {
      return <Component { ...this.props } { ...this.state } />;
    }
  }
const API = 'https://hn.algolia.com/api/v1/search?query=';
const DEFAULT_QUERY = 'redux';
...
// 使用”高阶组件“
const AppWithFetch = withFetching(API + DEFAULT_QUERY)(App);

高阶组件本质是在接收的组件基础上,增加了一些额外的功能。

jtwang7 commented 3 years ago

How to fetch data with React Hooks?

参考文章: How to fetch data with React Hooks?

注意事项

使用 React Hooks 处理数据请求,思想上同 React Class 组件类似,请求数据并将其存储在 state 中,通过 setState 触发重渲染以更新页面。有以下几点区别:

function App() { const [data, setData] = useState({ hits: [] });

// WRONG useEffect(async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); // 此处会隐式返回一个 Promise 对象,但 useEffect 内第一参数只能返回 null 或一个用于清除的回调函数。 }, []);

// RIGHT useEffect(() => { // 避免直接将 useEffect 第一参数声明为 async 函数 const fetchData = async () => { const result = await axios( 'https://hn.algolia.com/api/v1/search?query=redux', ); setData(result.data); }; // 执行 fetchData(); // useEffect 第二参数为 [],避免死循环 }, []);

return ( ... ); }

export default App;


## 自定义 Hook
同”高阶组件“一样,在函数组件中,可以通过将数据请求逻辑提升到自定义 Hook 中,实现逻辑的复用。
```js
// 将逻辑提取为一个函数,同时返回外界需要的结果(对外暴露操作接口)。
const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);
  // 用 useReducer 整合多个 useState。
  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });
      try {
        const result = await axios(url);
        // 获取数据后,通过 payload 传入 reducer,并调用相应的 action 修改数据。
        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };
    fetchData();
  }, [url]);

  ...

  return [state, setUrl];
};
// reducer 设计:通过 payload 接收请求的数据结果,更新到 state 中。
const dataFetchReducer = (state, action) => {
  switch (action.type) {
    case 'FETCH_INIT':
      return {
        ...state,
        isLoading: true,
        isError: false
      };
    case 'FETCH_SUCCESS':
      return {
        ...state,
        isLoading: false,
        isError: false,
        data: action.payload,
      };
    case 'FETCH_FAILURE':
      return {
        ...state,
        isLoading: false,
        isError: true,
      };
    default:
      throw new Error();
  }
};