这篇上面的一些方法是整理了
aHooks
中的一部分方法,为了方便自己快速查阅和使用,每个方法都有官方链接,示例可以参考官网。此文中的方法,一部分是自己比较常用的,一部分是我个人认为比较实用,也尽量不重复造轮子(下面的方法中,部分代码没有在项目中用过的方法,是直接copy
的官网,关于自己用过的会有添加注释,如果没有,大家以官网的解释为主哦!)
!!!!! 原来的Umi hooks
项目整合重新升级,现在使用的Umi hooks
有了新的替代品aHooks
,原来使用Umi Hooks
的同学可以跟着升级,基本上就是引入的包名换了一下,其他的有细微更改,下文中我也跟着做了些更改,大家使用的时候注意就可以哦!
// umi hooks 的引入方式
import { useDebounce } from '@umijs/hooks';
// ahooks 的引入方式
import { useRequest } from 'ahooks';
如果看到这篇文的你,恰好也是也在用react
的umi
库的小伙伴,我是大力推荐使用umi Hooks
,是真的香!
一、Async
之 useRequest
异步数据请求
import { useRequest } from 'ahooks';
1.自动请求
const { data } = useRequest(
async () => {
const response = await getList();
return response;
}
);
2.手动请求:manual
、run
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
可处理是否全屏的功能
- 使用
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>
);
};
- 传入
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>
</>
);
};
下面我们就点击更新来看一看是否有刷新:
如果要使用在有接口使用的页面时,建议在接口未请求完成前添加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
中也有form
,antd4
中用的是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;
接下来我们就点击按钮试试,我们看是能拿到校验的提示,但是这个提示打在了控制台?不应该显示在页面上吗???
当然validateFields
的写法也可以换成以下的写法:
const values = await form.validateFields();
console.log(values);
但是,但是,我要的提示它就是不出来,如果有路过的亲知道我这样写问题出在哪里,麻烦告诉我哈!!!
antd
的form
下面我们来试试
antd
的form
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>
我要的提示终于有了!!!!
通过 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>
下面我们来看一下页面展示和操作:
关于上面的问题,可能有其他的配置方法,antd4
的form
的属性相对antd3
来说多了很多,后续我再继续研究吧。
5. Recoil
:用于 React
的状态管理库
Recoil
提供了关于React
的状态处理,使父子组件的状态统一变得容易起来,类似redux
使用场景(引用阮大的解释):
- 某个组件的状态,需要共享
- 某个状态需要在其他组件可以拿到
- 一个组件需要改变全局状态
- 一个组件需要改变另一个组件的状态
然而,它们有一个共同的前提,那就是需要有一个共同的根组件来包裹,根组件下的所有组件均可以状态共享,废话不多说,下面上代码:
npm 下载:
cnpm install recoil
or
yarn add recoil
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, // 默认值
});
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;
selector
根据传递给函数的选项,返回可写或只读反冲状态。有
get
和set
两个函数,分别对应获取和设置,支持异步
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;
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;
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
!