前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >万万没想到react请求数据花样如此之多

万万没想到react请求数据花样如此之多

原创
作者头像
brzhang
发布2020-05-02 10:01:59
1.3K0
发布2020-05-02 10:01:59
举报
文章被收录于专栏:玩转全栈玩转全栈

接触react一个星期,也慢慢熟悉了一些概念,比如HOC(高阶组件)、jsx、函数式组件、HOOK,感觉react也没有别人说的学习曲线陡峭,难上手等等,给我的感觉,如果你会Vue,上手React真的会非常快,不要被这些概念给吓到,这样的一些概念的出现,一定是有着他的道理的,无外乎包含但不限于以下两点理由:

  1. 为了代码复用,比如HOC,自定义HOOK
  2. 为了代码更加简洁,更加好理解,比如jsx,函数式组件。

那么,我们所理解的React的模式,其实归根结底就是UI=Render(State),这其实和Vue乃至整个前端的哲学并无任何冲突,相反,是一个统一。说来说去,说简单点,一个web应用,应该是状态驱动的,而状态=数据+逻辑所以,我们的UI=Render(Data+Logic)

那么,Data从何而来,可以说99%的web应用的Data是从网络而已,俗称网络获取数据。

下面的代码段是一个很简单的显示列表数据模板,很简单,这里只用到了useState这个Hook,如果需要填充数据,很明显,使用setData给到数据就可以了,数据从何而来,这是一个问题,带到今天来看,要讲的是如何从网络获取数据。

代码语言:txt
复制
import React, { useState } from 'react';

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

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

温馨提示,下面是的列车将一步一步提速,请系好安全带~~。

引入axios请求网络数据,将请求放入useEffect中
代码语言:txt
复制
import React, { useState, useEffect } from 'react';
import axios from 'axios';

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

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

    setData(result.data);
  });

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

如果你熟悉 React class 的生命周期函数,你可以把 useEffect Hook 看做 componentDidMountcomponentDidUpdate 和 componentWillUnmount 这三个函数的组合,上述代码你应该不会满意吧,你可能仅仅需要网络请求代码只在componentDidMount的时候执行一次。如是

加了一个[]
代码语言:txt
复制
import React, { useState, useEffect } from 'react';
import axios from 'axios';

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

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

    setData(result.data);
  +++}, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;

通过Object.is(value1, value2);成功挡住了componentDidUpdate每次无脑的唤醒。

然而,上面的代码会有一个告警

那是因为useEffect要求要么返回一个清理函数,要么啥都别返回,而上面返回的是一个Promise体,他将最终返回的是一个结果,这显然会受到一个告警,解决的办法如下。

代码语言:txt
复制
import React, { useState, useEffect } from 'react';
import axios from 'axios';

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

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

      setData(result.data);
    };

    fetchData();
  }, []);

  return (
    <ul>
      {data.hits.map(item => (
        <li key={item.objectID}>
          <a href={item.url}>{item.title}</a>
        </li>
      ))}
    </ul>
  );
}

export default App;
不过很快就会就想到,网络请求需要传递参数

所以,你加了一个query的useState,而且仅仅当query变化时触发重新获取网络数据,干得还不错。

代码语言:txt
复制
...

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

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(
        `http://hn.algolia.com/api/v1/search?query=${query}`,
      );

      setData(result.data);
    };

    fetchData();
  }, [query]);

  return (
    ...
  );
}

export default App;
后面,你发现网络请求有点慢,需要加loading状态,如是
代码语言:txt
复制
import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux');
  const [url, setUrl] = useState(
    'https://hn.algolia.com/api/v1/search?query=redux',
  );
  const [isLoading, setIsLoading] = useState(false);

  useEffect(() => {
    const fetchData = async () => {
      setIsLoading(true);

      const result = await axios(url);

      setData(result.data);
      setIsLoading(false);
    };

    fetchData();
  }, [url]);

  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />
      <button
        type="button"
        onClick={() =>
          setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`)
        }
      >
        Search
      </button>

      {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;

其实很简单,就加了一个loading的useState而已。所以,如果需要加什么错误状态,你应该也懂这个套路了吧。

请注意,要开车了,前面说到,更好复用才是推动技术变革的第一生产力,比如Docker,我瞎扯的。

假如其他业务需要用到你这个网络请求,如是,你写了一个自定义的Hook

代码语言:txt
复制
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];
}
后面你知道了useReducer,你发现,loading,error这些状态应该交给他来做,而不是你通过useState来做,这样显得会更加清晰~~

因此,你引入了useReducer

代码语言:txt
复制
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();
  }
};

const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  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);

        dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
      } catch (error) {
        dispatch({ type: 'FETCH_FAILURE' });
      }
    };

    fetchData();
  }, [url]);

  ...
};

像这种语法

代码语言:txt
复制
return {
        ...state,
        isLoading: true,
        isError: false
      };

我们已经见怪不怪了,但是别小看这种,说大点,这可能是最简洁的函数式编程了,他返回的是一个全新的对象,函数式编程的好处?复用性无话可说,方便做备忘录,使用一个history数组记录每次变更的state就OK啦。anymore,自己YY吧。

最后,你可能会想,页面componentWillUnmount时,如果网络请求没回,是不是该”终止网络请求“
代码语言:txt
复制
const useDataApi = (initialUrl, initialData) => {
  const [url, setUrl] = useState(initialUrl);

  const [state, dispatch] = useReducer(dataFetchReducer, {
    isLoading: false,
    isError: false,
    data: initialData,
  });

  useEffect(() => {
    let didCancel = false;

    const fetchData = async () => {
      dispatch({ type: 'FETCH_INIT' });

      try {
        const result = await axios(url);

        if (!didCancel) {
          dispatch({ type: 'FETCH_SUCCESS', payload: result.data });
        }
      } catch (error) {
        if (!didCancel) {
          dispatch({ type: 'FETCH_FAILURE' });
        }
      }
    };

    fetchData();

    return () => {
      didCancel = true;
    };
  }, [url]);

  return [state, setUrl];
};

如是,你在useEffect中加了一个didCancel变量,并且返回一个闭包,其实就是一个函数啦,只不过他让你可以改他母体的变量而已,因此,在componentWillUnmount时候,这个变量被置位false了,如是dispatch的将不会触发。

是不是和移动客户端开发灰常像,页面的destory的时候,如果网络请求的presenter还持有页面的context,那么页面将释放不掉,造成内存泄漏不说,还会导致在页面执行destory之后,网络数据回来,走触发变更ui的逻辑,导致crash的发生,因为你不能对一个已经destory的页面进行变更ui的操作。

当然,这里,网络请求其实并没有真的被cancel掉,cancel掉的之后网络请求回来之后的逻辑。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引入axios请求网络数据,将请求放入useEffect中
  • 加了一个[]
  • 然而,上面的代码会有一个告警
  • 不过很快就会就想到,网络请求需要传递参数
  • 后面,你发现网络请求有点慢,需要加loading状态,如是
  • 请注意,要开车了,前面说到,更好复用才是推动技术变革的第一生产力,比如Docker,我瞎扯的。
    • 后面你知道了useReducer,你发现,loading,error这些状态应该交给他来做,而不是你通过useState来做,这样显得会更加清晰~~
      • 最后,你可能会想,页面componentWillUnmount时,如果网络请求没回,是不是该”终止网络请求“
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档