React 自定义Hook的思想

阅读前提:希望你对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。

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


作者:Liter
链接:https://juejin.im/post/5e4a5557e51d4526d326b0a3
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值