React 自定义Hook的思想

React 自定义Hook的思想

技术杂谈小彩虹2021-08-17 20:24:36160A+A-

阅读前提:希望你对react-hook的官方文档有几次阅读。

下面我基本是拿了人家的代码直接过来自己执行。该骂看起来不难,重要的是人家的思路值得学习。 如果想看原味的请移步这里参考地址,仔细看完你会觉得太美了。

下面的代码是封装类似于useHttp的自定义hook。

useState基本使用方法

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请求

axios最基本的使用

import React, { useState, useEffect } from 'react';
import axios from 'axios’; function App() { const [data, setData] = useState({ hits: [] }); useEffect(async () => { const result = await axios( 'http://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; 

只要组件渲染一次 useEffect就会重新执行一次 会循环发送请求

可以替代原来react生命周期的componentDidMount

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

  setData(result.data);

}, []);  // 每次传递一个空数组  只有第一次初始化组件的时候 才会渲染

组件执行的时候会报错 因为 useEffect不支持async异步的函数

修改之后

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

  fetchData();
}, []);

hook函数已经替代componentDidMount这个生命周期函数钩子


如何去触发hook这个钩子函数

我们使用input的输入 替代state的变化 进行触发useEffect函数钩子

query发生变化的时候 自动发送请求

import React, { Fragment, useState, useEffect } from 'react';
import axios from 'axios';

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=redux',
      );

      setData(result.data);
    };

    fetchData();
  }, []);
  return (
    <Fragment>
      <input
        type="text"
        value={query}
        onChange={event => setQuery(event.target.value)}
      />

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

export default App;

useEffect现在只有初始化的时候执行一次

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

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

现在 当我们在input输入文字的时候 需要触发这个钩子函数 useEffect的第二个参数 中的state发生变化的时候 就会自动触发该钩子函数重新执行一次

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

    setData(result.data);
 },[query])     // 添加需要触发这个钩子的state

只要query发生变化。这个函数就会重新执行 现在已经实现每次输入一个字符 就会发送一个请求

接下来需要 添加一个按钮 去获取input输入的字符 然后点击按钮的时候去触发useEffect

function App() {
  const [data, setData] = useState({ hits: [] });
  const [query, setQuery] = useState('redux’); const [search, setSearch] = useState(''); useEffect(() => { const fetchData = async () => { const result = await axios( `http://hn.algolia.com/api/v1/search?query=${query}`, ); setData(result.data); }; fetchData(); }, [query]); return ( <Fragment> <input type="text" value={query} onChange={event => setQuery(event.target.value)} /> <button type="button" onClick={() => setSearch(query)}> Search </button> <ul> {data.hits.map(item => ( <li key={item.objectID}> <a href={item.url}>{item.title}</a> </li> ))} </ul> </Fragment> ); } 

每次input输入的时候 会触发query的变化。button点击的时候 将query设置成 search

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

    setData(result.data);
  };

  fetchData();
}, [search]);

现在只有search发生变化的时候 才会触发useEffect触发 search要触发的话 只有点击button的时候才能获取

此处将URL单独提取出来

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

  useEffect(() => {
    const fetchData = async () => {
      const result = await axios(url);

      setData(result.data);
    };

    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>

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

在hook中添加loadding

请求之前进行loadding的设置 并且渲染到ui中

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(
    'http://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;

HOOK中添加捕获错误的显示组件

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(
    'http://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 (
    <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>

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

使用form表单进行提交表单

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

  {isError && <div>Something went wrong ...</div>}

  …
</Fragment>

我们需要阻止默认的表单提交动作

<form onSubmit={event => {
  setUrl(`http://hn.algolia.com/api/v1/search?query=${query}`);
  event.preventDefault();
}}>

自定义HOOK 将hook的动作提取出来进行单独的封装调用

const useHackerNewsApi = () => {
  const [data, setData] = useState({ hits: [] });
  const [url, setUrl] = useState(
    'http://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];
}

使用useHackerNewsApi

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


此时还没有初始化请求的url 通过传递参数 进行初始化请求参数

function App() {
  const [query, setQuery] = useState('redux');
  const [{ data, isLoading, isError }, doFetch] = useDataApi(
    'http://hn.algolia.com/api/v1/search?query=redux',
    { hits: [] },
   );
...

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

使用Reducer Hook来提取数据

在useEffect中多次设置state的状态, 不方便调整。使用reduce将整个数据的状态进行集中管理设置

import React, {
  Fragment,
  useState,
  useEffect,
  useReducer,   // 引入usrReducer
} from 'react';
import axios from 'axios';

const dataFetchReducer = (state, action) => { // 定义useReducer触发函数
...
};

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

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

APP组件中的useEffect

useEffect(() => {
  const fetchData = async () => {
    dispatch({ type: 'FETCH_INIT' }); // 使用dispatch派发设置state

    try {
      const result = await axios(url);

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

  fetchData();
}, [url]);

使用useDataApi进行封装之后

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

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

...

  return [state, setUrl];
};

dataFetchReducer 设置返回的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();
  }
};

设置didCance 当多个组件公用一个自定义的hook的时候 得防止未安装的组件对状态的设置


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];
};

参考地址 这是去年八月那会的笔记 今天总结到掘金,自己返回来再看看基础的知识 HOOK插件地址 这里面的hook-api都是大佬封装的 源码打开仔细去看我发现为啥人家就能年入百几万,我们就这么low。

最后对自己说一句:学习渠道和学习方式很重要。 如果有什么好的学习渠道和方式求大家评论下方推荐给我,我们相互学习一波。

点击这里复制本文地址 以上内容由权冠洲的博客整理呈现,请务必在转载分享时注明本文地址!如对内容有疑问,请联系我们,谢谢!

支持Ctrl+Enter提交

联系我们| 本站介绍| 留言建议 | 交换友链 | 域名展示 | 支付宝红包
本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除

权冠洲的博客 © All Rights Reserved.  Copyright quanguanzhou.top All Rights Reserved
苏公网安备 32030302000848号   苏ICP备20033101号-1
本网站由 提供CDN/云存储服务

联系我们