推荐好用的React hooks库

7,198 阅读18分钟

这篇上面的一些方法是整理了aHooks中的一部分方法,为了方便自己快速查阅和使用,每个方法都有官方链接,示例可以参考官网。此文中的方法,一部分是自己比较常用的,一部分是我个人认为比较实用,也尽量不重复造轮子(下面的方法中,部分代码没有在项目中用过的方法,是直接copy的官网,关于自己用过的会有添加注释,如果没有,大家以官网的解释为主哦!)

!!!!! 原来的Umi hooks 项目整合重新升级,现在使用的Umi hooks 有了新的替代品aHooks,原来使用Umi Hooks 的同学可以跟着升级,基本上就是引入的包名换了一下,其他的有细微更改,下文中我也跟着做了些更改,大家使用的时候注意就可以哦!

// umi hooks 的引入方式
import { useDebounce } from '@umijs/hooks';

// ahooks 的引入方式
import { useRequest } from 'ahooks';

如果看到这篇文的你,恰好也是也在用reactumi库的小伙伴,我是大力推荐使用umi Hooks,是真的香!

Umi Hooks

aHooks

一、AsyncuseRequest 异步数据请求

import { useRequest } from 'ahooks';

1.自动请求

  const { data } = useRequest(
    async () => {
      const response = await getList();
      return response;
    }
  );

2.手动请求:manualrun

 const { data: list, run: listRequest } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      manual: true,
    },
  );

  useEffect(() => {
    if('某些条件'){
    listRequest();
    }
  }, []);

3. 依赖请求:ready

ready接收一个布尔值,只有ready的值变为true时,才会发起请求,实现依赖请求

 const { data, run, cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      ready: Boolean(isOk),
    },
  );

4. 手动暂停请求:cancel

 const { data, run, cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      manual: true,
    },
  );

<Button.Group>
    <Button onClick={run}>开始请求</Button>
    <Button onClick={cancel}>停止请求</Button>
</Button.Group>

5.请求成功后执行的某些操作:onSuccess

  const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      onSuccess: res => {
        // 这里的res是拿到的接口返回的所有数据
        message.success('请求成功')
      },
    },
  );

6.请求失败后执行的某些操作:onError

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      onError: (error) => {
        // 这里的error是拿到的接口返回的错误信息
        message.success('请求失败')
      },
    },
  );

7. 请求过程中的loading

  const { data: authList,loading } = useRequest(
    async () => {
      const response = await getAuthList();
      return response;
    }
  );

8. initialData当请求未返回时,默认的data数据

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      initialData:[],
    },
  );

9. 请求参数变化触发接口请求:refreshDeps

refreshDeps参数变化,会触发service,重新执行请求,只适用于简单情况,当需要经过一些判断再触发请求时,建议还是放在useEffect

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      refreshDeps:[state1,state2],
    },
  );

注意:默认会自动请求一次,当依赖多个参数时,并不会合并参数的变化发起请求

10. 格式化返回的结果:formatResult

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      formatResult: (res) => {
        // 这里的res是拿到的接口返回的数据
        const list = [];
        res.forEach(item => {
          list.push(item.id);
        });
        return list;
      },
    },
  );

11.直接修改接口返回的data数据:mutate

mutate:可能只截取一部分的data,或者根据接口返回的数据,自定义返回内容。 formatResult:原则上是用来做格式化,对数据进行重新整理

  const { data,mutate } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      onSuccess: res => {
        // 这里的res是拿到的接口返回的所有数据
        message.success('请求成功')
        mutate([{ id: 111, name: '测试' }]);
      },
    },
  );

12.自动执行 run 的时候,默认带上的参数:defaultParams

只适用于初始且自动请求带的参数

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      defaultParams: [{ id: defaultList }],
    },
  );

以下的写法也可以:

 const { data } = useRequest(
    async () => {
      const response = await getList({ id: defaultList });
      return response;
    },
  );

13. 屏幕聚焦,重新发起请求,refreshOnWindowFocus

默认为false,如果设置为 true,在屏幕重新聚焦或重新显示时,会重新发起请求。

 const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      refreshOnWindowFocus: true
    },
  );

focusTimespan: 屏幕重新聚焦,如果每次都重新发起请求,不是很好,我们需要有一个时间间隔,在当前时间间隔内,不会重新发起请求,需要配置refreshOnWindowFocus使用,默认:5000

const { data } = useRequest(
    async params => {
      const response = await getList(params);
      return response;
    },
    {
      refreshOnWindowFocus: true,
      focusTimespan:10000,
    },
  );

14. 接口防抖:debounceInterval

适用于多次手动频繁请求run,设置的防抖拦截,最后一次请求发起后的n秒后,才会发起真正的接口请求

 const { data, run,cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      manual: true,
      debounceInterval: 500,
    },
  );

<Button.Group>
    <Button onClick={run}>发起请求</Button>
</Button.Group>

15. 节流:throttleInterval

适用于多次手动频繁请求run,设置的节流拦截,一定时间内只触发一次接口请求

 const { data, run,cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      manual: true,
      throttleInterval: 500,
    },
  );

<Button.Group>
    <Button onClick={run}>发起请求</Button>
</Button.Group>

16. 轮询请求:pollingInterval

设置后会进入轮询模式,定时触发函数执行

 const { data, run,cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      pollingInterval: 500,
    },
  );

pollingWhenHidden:屏幕隐藏时是否会继续轮询,默认为 true,当屏幕不可见时,再发起轮询任务就很浪费,可以设置,当屏幕不可见,暂停定时任务

 const { data, run,cancel } = useRequest(
    async () => {
      const response = await getList();
      return response;
    },
    {
      pollingInterval: 500,
      pollingWhenHidden: false
    },
  );

二、防抖&&节流

1. useDebounce:用来处理防抖值的 Hook

用来处理防抖值的Hook

官网示例

import { Input } from 'antd';
import React, { useState } from 'react';
import { useDebounce } from 'ahooks';
export default () => {
  const [value, setValue] = useState();
  const debouncedValue = useDebounce(value, { wait: 500 });
  return (
    <div>
      <Input
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>DebouncedValue: {debouncedValue}</p>
    </div>
  );
};

2. useDebounceFn:用来处理防抖函数的 Hook

用来处理防抖函数的 Hook

官网示例

处理handleSubmit 函数的hooks,可以在这里处理一些判断,发起请求。当handleSubmit一定时间内被多次调用,但只在最后一次点击后的1000毫秒,会进入到函数内

  const { run: handleSubmit } = useDebounceFn(() => {
   
  // dosomething...
  },  {
        wait: 1000,
    },);

3. useThrottle:用来处理值节流的Hook

用来处理值节流的Hook

官方示例

import { Input } from 'antd';
import React, { useState } from 'react';
import { useThrottle } from 'ahooks';
export default () => {
  const [value, setValue] = useState();
  const throttledValue = useThrottle(value, {wait:500});
  return (
    <div>
      <Input
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>throttledValue: {throttledValue}</p>
    </div>
  );
};

4. useThrottleFn:用来处理函数节流的Hook

用来处理函数节流的Hook

官网示例

处理handleSubmit 函数的hooks,可以在这里处理一些判断,发起请求。当handleSubmit一定时间内被多次调用,1000秒内只会进入到函数内1

  const { run: handleSubmit } = useDebounceFn(() => {
   // dosomething...
  
  }, {
        wait:1000
      
  });

三、LifeCycle

1. useMount:只在组件 mount 时执行的 hook

在组件首次渲染时,执行方法。

官网示例

  useMount(() => {
    console.log('mount');
  });

等同于初次加载的useEffect没有依赖的时候:

  useEffect(() => {
    console.log('mount');
  }, []);

2. useUnmount:只在组件unmount时执行的hook

在组件卸载时,执行方法。

官网示例

  useUnmount(() => {
    // 可以在卸载的时候,处理取消计时器等一些操作
    console.log('卸载');
  });

3. useUpdateEffect:一个只在依赖更新时执行的 useEffect hook

之前用useEffect的时候,一直有一个困惑,就是useEffect无论是否有依赖项,首次渲染都会进入到函数内,为了避免这样尴尬的情况,每次都需要用if 判断来拦截。useUpdateEffect就解决了这个痛点,它在使用上同useEffect完全一样,只是它忽略了首次渲染,且只在依赖项变化时进行运行。

官网示例

useEffect:

  useEffect(() => {
  // 首次会运行
    console.log('tabkey', tabKey);
  }, [tabKey]);

useUpdateEffect:

  useUpdateEffect(() => {
  // 首次不运行,只有在依赖更新的时候运行,当第二个参数为空,也不会运行
    console.log('tabkey', tabKey);
  }, [tabKey]);

总结:如果你需要首次运行,监听到相应参数也运行,那就用useEffect, 如果你想要的结果是首次不运行,只有在监听到参数变化后再运行,那就用useUpdateEffect

四、Dom

1. useFullscreen :一个用于处理 dom 全屏的 Hook

可处理是否全屏的功能

  1. 使用ref 设置需要全屏的元素

官方示例

import React from 'react';
import { Button } from 'antd';
import {useFullscreen} from 'ahooks';
export default () => {
  const { ref, isFullscreen, setFull, exitFull, toggleFull } = useFullscreen<HTMLDivElement>();
  return (
    <div ref={ref} style={{ background: 'white' }}>
      <div style={{ marginBottom: 16 }}>{isFullscreen ? 'Fullscreen' : 'Not fullscreen'}</div>
      <Button.Group>
        <Button onClick={setFull}>setFull</Button>
        <Button onClick={exitFull}>exitFull</Button>
        <Button onClick={toggleFull}>toggle</Button>
      </Button.Group>
    </div>
  );
};
  1. 传入 function 来监听任意的 dom 节点

可用于图片,指定元素的全屏

import React from 'react';
import { Button } from 'antd';
import {useFullscreen} from 'ahooks';
import img from './react-hooks.jpg';
export default () => {
  const { setFull } = useFullscreen<HTMLElement>({
    dom: () => document.getElementById('fullscreen-img'),
  });
  return (
    <div style={{ background: 'white' }}>
      <div style={{ marginBottom: 16 }}>
        <img id="fullscreen-img" src={img} style={{ width: 320 }} alt="" />
      </div>
      <Button onClick={setFull}>setFull</Button>
    </div>
  );
};

2.useHover : 一个用于追踪dom元素是否有鼠标悬停的Hook

可以使用ref来设置需要监听的元素,并获取到状态,进行一些操作

官方示例

import React from 'react';
import { useHover } from 'ahooks';
export default () => {
  const [isHovering, hoverRef] = useHover<HTMLDivElement>();
  return <div ref={hoverRef}>{isHovering ? 'hover' : 'leaveHover'}</div>;
};

同样也支持拿到悬停的回调,这样你可以根据不同回调来执行不同操作

import React from 'react';
import { useHover } from 'ahooks';
export default () => {
  const [isHovering] = useHover({
    dom: () => document.getElementById('hover-div'),
    onEnter: () => {
      console.log('onEnter');
    },
    onLeave: () => {
      console.log('onLeave');
    },
  });
  return <div id="hover-div">{isHovering ? 'hover' : 'leaveHover'}</div>;
};

3.useMouse:一个跟踪鼠标位置的Hook

可以实时拿到鼠标的位置

官方示例

import React, { useMemo } from 'react';
import {useMouse} from 'ahooks';
export default () => {
  const mouse = useMouse();
  return <div>Mouse Pos: {JSON.stringify(mouse)}</div>;
};

4. useInViewport:一个用于判断dom元素是否在可视范围之内的 Hook

适用于有些业务场景,如果一个dom元素不在可是范围之内,触发其他dom的变化,使用 ref 监听节点在视图变化或者滚动时是否在可视范围之内

官方示例

import React from 'react';
import {useInViewport} from 'ahooks';
export default () => {
  const [inViewPort, ref] = useInViewport<HTMLDivElement>();
  // 同样可以使用id来设置绑定元素
  // const [inViewPort] = useInViewport(() => document.querySelector('#demo2'));
  return (
    <div>
      <div ref={ref} id="dome2">observer dom</div>
      <div style={{ marginTop: 70, color: inViewPort ? '#87d068' : '#f50' }}>
        {inViewPort ? 'visible' : 'hidden'}
      </div>
    </div>
  );
};

5. useSize:一个用于监听dom节点尺寸变化的hook

可以实时拿到监听元素的宽、高

官方示例

import React from 'react';
import {useSize} from 'ahooks';
export default () => {
  const [state] = useSize(document.querySelector('body'));
  // 同样可以用 id 来绑定:
  //  const [state] = useSize(() => document.querySelector('#demo2'));
  return (
    <div id="demo2">
      this demo is listening to body size change, try to resize the window instead <br />
      dimensions -- width: {state.width} px, height: {state.height} px
    </div>
  );
};

6. useTextSelection:实时获取用户当前选取的文本内容及位置

可以拿到选取的文本值、上下左右坐标、高度、宽度

官方示例

import React from 'react';
import { useTextSelection } from 'ahooks';
export default () => {
  const [selection, ref] = useTextSelection();
  //  通过id 来监听
  //  const [{ text  }] = useTextSelection(() => document.querySelector('#target-dom'));
  return (
    <div>
      <div ref={ref}>
        <p id="target-dom">
          Please swipe your mouse to select any text on this paragraph.
        </p>
      </div>
      <p>Result:{JSON.stringify(selection)}</p>
    </div>
  );
};

6. useUpdate : 会返回一个函数,调用函数会强制组件重新渲染

当我们有些场景需要刷新页面,但又不想让用户等待,即可以使用它,它可以提供无感刷新的感受

官方示例

import React from 'react';
import { useUpdate } from 'ahooks';

export default () => {
  const update = useUpdate();
  
  useEffect(() => {
    console.log(233);
  }, []);

  return (
    <>
      <div>Time: {Date.now()}</div>
      <button type="button" onClick={update} style={{ marginTop: 8 }}>
        update
      </button>
    </>
  );
};

image.png

下面我们就点击更新来看一看是否有刷新:

image.png

如果要使用在有接口使用的页面时,建议在接口未请求完成前添加loading效果。


7. useLockFn : 用于给一个异步函数增加竞态锁,防止并发执行。

模拟使用场景:有一个输入表单,表单有一个提交按钮,在正常情况,我们填写完成后,点击提交即可完成表单的提交操作。非正常情况:鼠标多点击了一下或者网络不太好,多点击了几下,那么提交按钮就会被触发多次,但这并不是我们想要的。

在未发现useLockFn之前,上面的情况我是用debounce来解决的,现在我认为useLockFn才是深得我心的,就像它的介绍一样,为函数添加一个锁,在函数执行完成前,其余的点击动作都会被忽略。

官方示例

import { useLockFn } from 'ahooks';
import { message } from 'antd';
import React, { useState } from 'react';

function mockApiRequest() {
  return new Promise<void>((resolve) => {
    setTimeout(() => {
      resolve();
    }, 2000);
  });
}

export default () => {
  const [count, setCount] = useState(0);

// 把需要执行的内容包在里面
  const submit = useLockFn(async () => {
    message.info('Start to submit');
    await mockApiRequest();
    setCount((val) => val + 1);
    message.success('Submit finished');
  });

  return (
    <>
      <p>Submit count: {count}</p>
      <button onClick={submit}>Submit</button>
    </>
  );
};

六、其他hooks

1. useBrowserContextCommunication : 同一页面在不同tab打开的状态同步

使用 Broadcast Channel API 为不同浏览器上下文(选项卡,iframe,窗口)之间的通信提供简单的解决方案。 useBrowserContextCommunication 官网地址

npm 下载:

cnpm install --save react-window-communication-hoo

具体代码示例:


import React ,{ useState } from 'react'
import useBrowserContextCommunication from 'react-window-communication-hook';


const App = ()=>{

  // communicationState 是一个 {lastMessage, messages} 对象,用于从其它的浏览器上下文接收数据

  const [communicationState, postMessage] = useBrowserContextCommunication('channel');

  const [status, setStatus] = useState('login');

  // 切换事件

  const logout=()=> {
    setStatus('logout');
    postMessage('logout');
  }

  const isLogout = [communicationState.lastMessage, status].includes('logout');

    return (
    <div>
      <h1>状态:{isLogout ? '已退出' : '已登录'}</h1>
      <button onClick={logout}>退出</button>
    </div>
  );
}

export default App;

接下来,请用上面这个页面用两个tab打开,在第一个页面操作后,看另一个页面的状态变化。

2. react-use-idb:给indexdb存储数据

参数:

const [value, setValue] = useIdb(key, initialValue)

value:获取对应key 的value 值

setValue:给对应的key 设置value值

key: key键值

initialValue:默认存储的value值

npm 下载:

cnpm  install --save react-use-idb

具体代码示例:

import { useIdb } from 'react-use-idb';

export default () => {
  const [value, setValue] = useIdb('key', 'ccc');

  return (
    <div>
      <div>Value: {value}</div>
      <button onClick={() => setValue('aaa')}>aaa</button>
      <button onClick={() => setValue('bbb')}>bbb</button>
    </div>
  );
};

3. @rehooks/online-status:用于获取当前的网络状态

useOnlineStatus 用于获取当前的网络状态,返回 true / false npm 下载:

cnpm install --save @rehooks/online-status

具体代码示例:

import { React } from 'react'
import useOnlineStatus from '@rehooks/online-status';

const App =()=>{
  const onlineStatus = useOnlineStatus();
  return (
    <div>
       <h1>当前网络状态 {onlineStatus ? '在线' : '离线'}</h1>
    </div>
  );
}
export default App;

4. 关于form表单提交的hook

rc-form-hooks

用于 React 高性能表单控件,自带数据域管理。包含数据获取、录入、校验等功能。

在我实际工作中,做的后台管理系统居多,在后台管理系统中表单提交是必不可少的,这一趴就分享一下我近两年一直使用的一个处理form表单的hook,自认为是使用起来较为灵活的hook

下载地址

常用的方法:

 getFieldDecorator 绑定表单组件,使之成为受控组件 

 validateFields  提交表单验证成功的回调 示例 validateFields().then(data)=>{}

 getFieldsValue  获取表单的values值集合,示例 const data = getFieldsValue();

 resetFields  重置表单  示例 resetFields()

 setFieldsValue 设置表单中的值,示例 setFieldsValue({ name:nickname , age:22})

 getFieldValue 获取某个具体受控表单的值,示例 let textContent = getFieldValue('content');

 ...
import useForm from 'rc-form-hooks';
import { Form, Row, Col, Input, Button } from '@rly/rly-p';

const form = useForm();

const { getFieldDecorator, validateFields, resetFields, setFieldsValue } = form;


 // 提交方法
 
  const handleUpdate = () => {
      // 当表单中有报错,则不会进这里
      
    validateFields().then((data) => {
    
      // 这里的data 返回的就是表单中所有的表单提交信息
      // dosomething...
    });
  };

      <Form {...formLayout}>
     
            <Form.Item label="版本类型">{getFieldDecorator('', {})(<span>安卓</span>)}</Form.Item>

            <Form.Item label="版本号">
              {getFieldDecorator('versionName', {
                rules: [
                  {
                    required: true,
                    message: '请输入版本号',
                  },
                ],
              })(<Input allowClear placeholder="请输入版本号" />)}
            </Form.Item>
        
            <Row>
              <Col span={2} offset={18}>
              
              // handleUpdate 提交方法写在哪里看你的业务需求即可,不用非要包在 form 里面
              // 如果写组件,就需要把提交按钮抽离出来,除此之外其他场景正常就写在form里面即可
              
                <Button type="primary" htmlType={'submit'} onClick={handleUpdate}>
                  确定
                </Button>
              </Col>

              <Col span={2} offset={1}>
                <Button
                  onClick={() => {
                    // 取消事件
                  }}
                >
                  取消
                </Button>
              </Col>
              
               <Col span={2} offset={1}>
                <Button
                  onClick={() => {
                  // 重置表单
                   resetFields()
                  }}
                >
                  重置
                </Button>
              </Col>
            </Row>
          
      </Form>

rc-field-form

当然antd中也有formantd4中用的是rc-field-form,在之前的项目中因项目庞大无法立刻升级到antd4,我们就单下载的 rc-field-form,但使用感受并不尽人意,下面我们就来写个示例:

关于文档,我只在npm 下载网站上找到了一份下载地址,大家如果有找到它的其他文档,麻烦给我留个言告诉我一下哈。

下面是一个官方示例,唯一改动点:加了一个必填选项的 rules,并定义了错误 message 的内容

import Form, { Field } from 'rc-field-form';

const Input = ({ value = "", ...props }) => <input value={value} {...props} />;

const Demo = () => {
  return (
    <Form
      onFinish={(values) => {
        console.log("Finish:", values);
      }}
    >
      <Field 
          name="username" 
          rules={[
              {
                required: true,
                message: '请输入名称',
              },
            ]}>
        <Input placeholder="Username" />
      </Field>
      <Field name="password">
        <Input placeholder="Password" />
      </Field>

      <button>Submit</button>
    </Form>
  );
};

export default Demo;

当我点击Submit时,并没有校验的必填提示,what??? 是我写的不对?它还推荐了另一种方式useForm函数的方式来获取结果,那么我现在来试试:

import Form, { Field, useForm } from 'rc-field-form';

const Input = ({ value = "", ...props }) => <input value={value} {...props} />;

const Demo = () => {


  const [form] = useForm();
  const { validateFields } = form;
  
 // 用validateFields 回调的方式来获取表单的所有值
 const onSubmit = () => {
    validateFields().then((data) => {
      console.log(data);
    });
 };
  
  
  return (
    <Form
     form={form}
    >
      <Field 
          name="username" 
          rules={[
              {
                required: true,
                message: '请输入名称',
              },
            ]}>
        <Input placeholder="Username" />
      </Field>
      <Field name="password">
        <Input placeholder="Password" />
      </Field>

        // 去掉这里的提交按钮,改换成用函数来承接
        
      {/* <button>Submit</button> */}
    </Form>
    
     <Button type="primary" onClick={onSubmit}>
          Submit
     </Button>
  );
};

export default Demo;

接下来我们就点击按钮试试,我们看是能拿到校验的提示,但是这个提示打在了控制台?不应该显示在页面上吗???

image.png

当然validateFields的写法也可以换成以下的写法:

 const values = await form.validateFields();
 console.log(values);

但是,但是,我要的提示它就是不出来,如果有路过的亲知道我这样写问题出在哪里,麻烦告诉我哈!!!

antdform

下面我们来试试antdform

import { Row, Form, Input, Button } from 'antd';

const onFinish = (values: any) => {
    console.log(values);
};

 <Form onFinish={onFinish}>
 
    <Form.Item
    label="Username"
    name="username"
    rules={[{ required: true, message: 'Please input your username!' }]}
    >
        <Input />
        
    </Form.Item>
    
    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
    
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
        
    </Form.Item>
    
    </Form>

我要的提示终于有了!!!!

image.png

通过 Form.useForm 对表单数据域进行交互。

import { Row, Form, Input, Button } from 'antd';

const [form] = Form.useForm();

const onFinish = (values: any) => {
    console.log(values);
};

 <Form form={form} onFinish={onFinish}>
 
    <Form.Item
    label="Username"
    name="username"
    rules={[{ required: true, message: 'Please input your username!' }]}
    >
        <Input />
        
    </Form.Item>
    
    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
    
        <Button type="primary" htmlType="submit">
          Submit
        </Button>
        
    </Form.Item>
    
    </Form>

和上面的使用是同样的结果,我们仍然能拿到返回的结果,但不要高兴的太早,上面👆都是我们常规操作,接下来我们试试更灵活的方式看看!!!!

我需要在 form 以外的地方来添加点击按钮,通过点击按钮的触发来获得表单结果,代码如下:

import { Row, Form, Input, Button } from 'antd';


const [form] = Form.useForm();


const onFinish = (values: any) => {
    console.log(values);
};


 <Form form={form} onFinish={onFinish}>
 
    <Form.Item
    label="Username"
    name="username"
    rules={[{ required: true, message: 'Please input your username!' }]}
    >
    
        <Input />
        
    </Form.Item>
    
    <Form.Item wrapperCol={{ offset: 8, span: 16 }}>
    
     // 不把点击事件的按钮放在form里面
        
    </Form.Item>
    
    </Form>

   // ...

   // 在页面的某个位置
   
   <Button type="primary" htmlType="submit">
      Submit
   </Button>


下面我们来看一下页面展示和操作:

image.png

关于上面的问题,可能有其他的配置方法,antd4form的属性相对antd3来说多了很多,后续我再继续研究吧。

5. Recoil:用于 React 的状态管理库

Recoil提供了关于React的状态处理,使父子组件的状态统一变得容易起来,类似redux

使用场景(引用阮大的解释):

  • 某个组件的状态,需要共享
  • 某个状态需要在其他组件可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

然而,它们有一个共同的前提,那就是需要有一个共同的根组件来包裹,根组件下的所有组件均可以状态共享,废话不多说,下面上代码:

npm 下载:

cnpm install recoil

or

yarn add recoil

文档地址

  1. RecoilRoot

组件使用 Recoil 状态之前需要在它的外面包裹一层RecoilRoot 组件。可以直接短平快地放在根组件外面:

import React from 'react';
import { Card } from 'antd';
import { RecoilRoot } from 'recoil';
import Test from './test';
import Test2 from './test2';
import Test3 from './test3';

const App = () => {

  return (
    <RecoilRoot>

      <Test />

      <Test2 />

      <Test3 />
      
    </RecoilRoot>
  );
};

export default App;

2.atom

atom 表示一小块状态。atom 可以在任意组件中进行读写。组件读取 atom 数据将会隐式订阅它,任何更新都会导致订阅它的组件进行重新渲染。

import { atom } from 'recoil';

export const atomTermId = atom({
  key: 'termId', // 键
  default: null, // 默认值
});

  1. useRecoilState

可以读取atom定义的状态,返回一个元组,其中第一个元素是atom的值,第二个元素是setter函数,可以在调用时将atom更新为给定的值。

import React from 'react';
import { Button, Card } from 'antd';
import { useRecoilState } from 'recoil';

const atomAuthId = atom({
  key: 'accountWechatId',
  default: null,
});

const Test = () => {
  const [stateAuthId, setStateAuthId] = useRecoilState(atomAuthId);

  const addCount = () => {
    const date = new Date().getMilliseconds();
    setStateAuthId(date);
  };

  return (
    <Card>
      <div style={{ marginBottom: 20 }}>Test-AuthId:{stateAuthId}</div>
      <Button type="primary" onClick={addCount}>
        改变-AuthId
      </Button>
    </Card>
  );
};

export default Test;
  1. selector

根据传递给函数的选项,返回可写或只读反冲状态。有getset两个函数,分别对应获取和设置,支持异步

import React from 'react';
import { Button, Card } from 'antd';
import { atom, selector, useRecoilState } from 'recoil';

const tempFahrenheit = atom({
  key: 'tempFahrenheit',
  default: 32,
});

// 通过 selector 的get 和 set 的执行来处理数据
const tempCelcius = selector({
  key: 'tempCelcdius',
  
  // get:手动获取或者设置后的回显都会走这里
  get: ({ get }) => {
    return get(tempFahrenheit) + 10; // 默认value 是 32,获取时 +10,初始默认是42
  },
  
  // set:如果不设置set ,就不能使用 useRecoilState 第二个参数来设置值
  set: ({ set }, newValue) => {
    return set(tempFahrenheit, newValue * 2); // 设置时value * 2
  },
});

const Test = () => {
  const [tempF, setTempF] = useRecoilState(tempFahrenheit);
  const [tempC, setTempC] = useRecoilState(tempCelcius);

  const addTenCelcius = () => setTempC(tempC);
  const addTenFahrenheit = () => setTempF(tempF + 10);

  return (
    <>
      <Card>
        Temp (Celcius): {tempC}
        <br />
        <Button onClick={addTenCelcius}>Add 10 Celcius</Button>
        <br />
      </Card>
      <Card>
        Temp (Fahrenheit): {tempF}
        <br />
        <Button onClick={addTenFahrenheit}>Add 10 Fahrenheit</Button>
      </Card>
    </>
  );
};

export default Test;

  1. useRecoilValue

当组件打算读取状态而不向其写入时,可以用它,返回给定recoil状态的值。

import React  from 'react'
import {atom, selector, useRecoilValue} from 'recoil';

const App = ()=>{

    //atom 状态
    const namesState = atom({
      key: 'namesState',
      default: ['', 'Ella', 'Chris', '', 'Paul'],
    });
    
    // selector 选择器处理
    const filteredNamesState = selector({
      key: 'filteredNamesState',
      get: ({get}) => get(namesState).filter((str) => str !== ''),
    });
    
    // 获取值
    const names = useRecoilValue(namesState);
    const filteredNames = useRecoilValue(filteredNamesState);

    return (
    <>
      Original names: {names.join(',')}
      <br />
      Filtered names: {filteredNames.join(',')}
    </>
  );
}

export default App;

  1. useResetRecoilState

重置recoil的值为初始值的函数,接收一个参数,并将其接收的recoil的值重置为定义时的default

import React from 'react';
import { Button, Card } from 'antd';
import { atom, useRecoilState, useResetRecoilState } from 'recoil';

const tempFahrenheit = atom({
  key: 'tempFahrenheit',
  default: 32,
});

const Test = () => {
  const [tempF, setTempF] = useRecoilState(tempFahrenheit);


  const addTenCelcius = () => setTempC(tempC);
  const addTenFahrenheit = () => setTempF(tempF + 10);

  // 重置为定义时的default值
  const resetList = useResetRecoilState(tempFahrenheit);

  return (
    <>
      <Card>
        Temp (Fahrenheit): {tempF}
        <br />
        <Button onClick={addTenFahrenheit}>Add 10 Fahrenheit</Button>
      </Card>

      <br />
      <Button onClick={resetList}>重置tempFahrenheit</Button>
    </>
  );
};

export default Test;


下面是多组件共用状态使用的具体代码(简版):

目录结构:

mods {
    index.ts 
        可以把共用的状态放在统一的一个文件,这样方便管理,当然也可以和上面的示例一样,把recoil 值的定义放在当前页面
}
index.tsx 
        根目录,把RecoilRoot放到这里
test.tsx
        共用状态的组件1
test2.tsx
        共用状态的组件2

mods-index.ts:

import { atom } from 'recoil';

export const atomTermId = atom({
  key: 'termId',
  default: null,
});

export const atomAuthId = atom({
  key: 'accountWechatId',
  default: null,
});

根目录index.tsx

import React, {  useEffect } from 'react';
import { Card,  } from 'antd';
import Test from './test';
import Test2 from './test2';
import { RecoilRoot, useRecoilState } from 'recoil';
import { atomAuthId, atomTermId } from './mods/index';

const State = () => {

  const [stateAuthId, setStateAuthId] = useRecoilState(atomAuthId);
  const [stateTermId, setStateTermId] = useRecoilState(atomTermId);

  useEffect(() => {
    setStateAuthId(1112);
    setStateTermId(1113);
 
  }, []);

  return (
    <Card>
      <Card>
        <p>index页面-- 初始值:</p>
        <p>stateAuthId:{stateAuthId}</p>
        <p>stateTermId:{stateTermId}</p>{' '}
      </Card>

      <Test />

      <Test2 />
     
    </Card>
  );
};

// 如果想在这里也能获取或设置recoil的值,那就像下面一样,把组件包一层,如果不需要,那就把RecoilRoot 提到上面包裹在最外层即可
const App = () => {
  return (
    <RecoilRoot>
      <State />
    </RecoilRoot>
  );
};
export default App;

test.tsx:

import React from 'react';
import { Button, Card } from 'antd';
import { atomAuthId } from './mods/index';
import { useRecoilState } from 'recoil';

const Test = () => {
  const [stateAuthId, setStateAuthId] = useRecoilState(atomAuthId);

  const addCount = () => {
    const date = new Date().getMilliseconds();
    setStateAuthId(date);
  };

  return (
    <Card>
      <div style={{ marginBottom: 20 }}>Test-AuthId:{stateAuthId}</div>
      <Button type="primary" onClick={addCount}>
        改变-AuthId
      </Button>
    </Card>
  );
};

export default Test;

test2.tsx:

import React from 'react';
import { Card, Button } from 'antd';
import { atomTermId } from './mods/index';
import { useRecoilState } from 'recoil';

const Test = () => {
  const [stateTermId, setStateTermId] = useRecoilState(atomTermId);

  const addCount = () => {
    const date = new Date().getMilliseconds();
    setStateTermId(date);
  };

  return (
    <Card>
      <div style={{ marginBottom: 20 }}>Test2-TermId:{stateTermId}</div>
      <Button type="primary" onClick={addCount}>
        更新-TermId
      </Button>
    </Card>
  );
};

export default Test;

页面展现:

备注: recoil的文档现在还不是很全,我把我用过的先推荐给大家,后期等文档更新了,我会再持续更新的哦!


接下来我会持续更新更多更好用的Hooks