本文共 7885 字,大约阅读时间需要 26 分钟。
notistack是React下Material UI框架的SnackBar(消息条)的高级用例,该用例能同时显示多个独立消息条,使用起来也非常简单。在全局初始化之后就可以在网页上任何需要的地方使用它,而不用为每个页面导入一个Material UI原生的消息条。
该用例是基于Material UI的SnackBar,做了一下功能和界面封装。下面我们从头来开始学习如何使用它。
我们的第一步照样是新建React工程,在工作目录下执行:
npx create-react-app notistackdemo
耐心等待安装完毕。因为notistack是基于Matrial UI,所以需要再安装相应的库:
cd notistackdemonpm install @material-ui/core --savenpm install @material-ui/icons --savenpm install notistack --save
好了,我们的准备工作完成了。
notistack提供了一个provider,在使用之前必须先初始化这个provider,首先我们将src/index.js
稍微增加一点内容改写成这样:
import React from 'react';import ReactDOM from 'react-dom';import './index.css';import App from './App';import * as serviceWorker from './serviceWorker';import { SnackbarProvider } from 'notistack'ReactDOM.render(, document.getElementById('root'));// If you want your app to work offline and load faster, you can change// unregister() to register() below. Note this comes with some pitfalls.// Learn more about service workers: https://bit.ly/CRA-PWAserviceWorker.unregister();
接下来,我们在主页面上增加一个按钮,用来点击显示消息条。修改src/App.js
,在导入语句中增加下面两行:
import Button from '@material-ui/core/Button';import { useSnackbar } from 'notistack';
然后在函数组件App里使用如下hook:
const { enqueueSnackbar } = useSnackbar()
最后再增加一个按钮和对应的处理程序,最后修改完成的App.js完整代码如下:
import React from 'react';import logo from './logo.svg';import './App.css';import Button from '@material-ui/core/Button';import { useSnackbar } from 'notistack';function App() { const { enqueueSnackbar } = useSnackbar(); const handleClick = event => { event.preventDefault(); enqueueSnackbar("This is a message",{ variant:"success"}); }; return ();}export default App;![]()
Edit
Learn Reactsrc/App.js
and save to reload.
运行npm start
,在主页面上点击按钮,就可以看到左下角会显示一个绿色的消息条了。好了,最基本的使用我们已经学习完成了,接下来我们学习一些稍微高级点的用法。
notistack的文档地址为:
从它的文档中我们看到可以自己设置很多属性,比如消息条的显示时间,关闭消息条后的回调函数等等。因为把全部属性在src/index.js
中设置并不是很整洁,于是我们新建一个NotistackWrapper
来进行集中设置。我们先ctrl + c
来关闭开发服务器的运行。
在src/
目录下新建NotistackWrapper.js
,里面代码如下:
//本JS进行一些notistack的常用设置import React from 'react';import { SnackbarProvider } from 'notistack';import { isMobile } from 'react-device-detect'/*** 显示的消息条的最大数量,如果超过,会关掉最先打开的然后再显示新的,是一个队列* 如果只想显示1个,设置为1,3是默认值*/const MAX_SNACKBAR = 3//设置自动隐藏时间,默认值是5秒,也就是5000毫秒const AUTO_HIDE_DURATION = 3000//设置消息条位置,默认值为底部左边const POSITION = { vertical: 'bottom', horizontal: 'left'}export default function NotistackWrapper({ children}) { return ({ children} )}
然后再改写src/index.js
,使用刚才自定义包装器来代替SnackbarProvider
,将第6行到第13行代码改为:
import NotistackWrapper from './NotistackWrapper.js'ReactDOM.render(, document.getElementById('root'));
这里我们做了移动端适配,移动端显示时让消息条底部靠边,用react-device-detect
库来判断是否移动端,需要进行安装。
npm install react-device-detect --save
让我们再次运行npm start
,大家可以自己对照一下文档更改一下参数,来查看一下效果。
有时,我们需要在消息条关闭时进行一些操作,比如提示完成之后的页面跳转等,这时就需要增加一个回调函数。
让我们改写一下src/App.js
中handleClick
的定义,在显示一个消息条时增加onClose
属性。
const handleClick = event => { event.preventDefault(); // variant could be success, error, warning, info, or default let options = { variant:"success", onClose:() => console.log("close a snackbar") }; enqueueSnackbar("This is a message", options); };
注意variant
属性只能是上面列举的几种值。
打开Chrome浏览器的开发者工具,在console那一栏就能看到我们打印的log。
注意
notistack这里有一个问题,使用上面的例子很容易看到,当我们点击按钮显示一个消息条时,点击屏幕任何位置,可以看到我们的log会输出close a snackbar
,也就是我们的onClose
回调函数会被触发一次。然后等消息条结束时,会又触发一次,再次打印出log,这才是onClose
回调函数需要真正执行的时候。我们在开发者工具里点击右键,清理console,然后在页面上点击按钮,就能清楚的复现我刚才提到的问题。
这个问题,也许是我没有仔细查看notistack的文档导致用法不对,也许是它本身的问题。我已经向作者发了邮件进行请教,在未得到作者的回应之前,让我们先采取一个临时措施来处理它。
打开node_modules\notistack\build\SnackbarItem
目录下的SnackbarItem.js
,找到第77行,也就是_this.handleClose
这个函数。修改它的定义,在第79行var snack = _this.props.snack;
上面增加以下代码片断: if(reason === _constants.REASONS.CLICKAWAY) { return;}
保存之后我们ctrl + c
,然后再次运行npm start
。这时可以看到,消息条出现时无论你点击屏幕多少下,都没有log输出了。
如果是我操作上存在的问题,请大家指正后我再更新文章。
补充
这个问题我和作者进行了几次邮件沟通,作者回信中指出这不是一个问题或者bug,而就是这样设计的,点击网页上任意地方(‘clickaway’)都会触发回调执行,因为有些人可能需要在此做一些处理。回调函数有两个参数event和reason,可以使用第二个参数来做判断和过滤。虽然我觉得任意点击执行关闭回调怪怪的,可以增加一个任意点击回调函数嘛,但是问题还是得到了解决。将上面的回调函数改写如下:
const handleClick = event => { event.preventDefault(); // variant could be success, error, warning, info, or default let options = { variant:"success", onClose:(_blank,reason) => { if(reason === 'clickaway') { return; } console.log("close a snackbar") } }; enqueueSnackbar("This is a message", options);};
这样,再次点击屏幕任意位置,代表回调执行的log就不再输出了。感谢作者的耐心回复。
有时用户可能不想等待消息条显示特定时间(比如3秒)而是想读完之后立刻关掉它。这时我们可以在消息条上增加一个关闭按钮。首先让我们修改src/NotistackWrapper.js
,在导入语句中增加关闭按钮导入:
import IconButton from '@material-ui/core/IconButton';import CloseIcon from '@material-ui/icons/Close';
然后我们再增加点击关闭功能,修改完成的最终代码如下:
//本JS进行一些notistack的常用设置import React from 'react';import { SnackbarProvider } from 'notistack';import { isMobile } from 'react-device-detect';import IconButton from '@material-ui/core/IconButton';import CloseIcon from '@material-ui/icons/Close';/*** 显示的消息条的最大数量,如果超过,会关掉最先打开的然后再显示新的,是一个队列* 如果只想显示1个,设置为1,3是默认值*/const MAX_SNACKBAR = 3//设置自动隐藏时间,默认值是5秒const AUTO_HIDE_DURATION = 10000//设置消息条位置,默认值为底部左边const POSITION = { vertical: 'bottom', horizontal: 'left'}export default function NotistackWrapper({ children}) { const notistackRef = React.createRef(); const onClickDismiss = key => () => { notistackRef.current.closeSnackbar(key); } return (( )})} > { children}
保存后页面会自动刷新,此时再点击按钮,出现的消息条就有一个关闭按钮了,点击这个按钮可以关掉它。
再次注意
等等,有问题?如果读者边读边操作可能会发现,手动点击关闭按钮关掉消息条时,没有打印log,也就是没有触发onClose
回调。这里我看了一下源码,点击这个按钮时并不是通过具体的SnackbarItem
本身来处理的,而是在它的上一层处理,所以就没有执行回调。这个问题我同样发邮件向作者请教了,也可能是我用法的问题。不过目前我们仍然采取一个临时措施来处理它。
我们需要修改node_modules\notistack\build\SnackbarProvider.js
这个文件,找到_this.handleCloseSnack
这个方法定义,在第202行左右,将它里面的_this.setState
那一段代码加上一点内容(大概217行下面),整个代码片断如下:
_this.setState(function (_ref3) { var snacks = _ref3.snacks, queue = _ref3.queue; return { snacks: snacks.map(function (item) { if (!shouldCloseAll && item.key !== key) { return _extends({ }, item); } if(item.key === key) { if(reason === null) { if(item.onClose) { item.onClose() } } } return item.entered ? _extends({ }, item, { open: false }) : _extends({ }, item, { requestClose: true }); }), queue: queue.filter(function (item) { return item.key !== key; }) // eslint-disable-line react/no-unused-state };});
然后保存,ctrl + c
之后再npm start
,这时你再点击消息条上的关闭按钮,就会有正确的log输出了。好了,我们来看完工之后的页面。
补充
原作者回信之后确认这是一个bug或者不完善的地方,承诺在下一版本修复它。当前notistack已经更新了,这个问题已经修复,不存在了。特此声明,感谢原作者。
我本来也写了一个类似notistack这样的一次初始化全局使用的消息条组件(很简单,并且只能显示一个消息条)并准备写在CSDN上。但是当我在Material UI 文档中看到了notistack的用法后我便立即决定抛弃我自己所写的组件而向大家推荐它(已经有更好的轮子了,何必自己再弄一个)。它和Material UI原生的消息条相比最大的优点是封装了多消息条队列,相互之间独立不影响。
本来最初只打算写一个notistack的最简单应用,后来想想已经写了就多加一点点功能吧,没想到增加的功能使用起来会有那么一点点小问题(目前是关闭时的回调函数调用)。这些问题也是我在写文章的过程中遇到的,因为我之前也没有用过,属于现学现写。示例很简单,虽然主体文件只有三个简单的js(不含修改的库文件),却也花了不少时间(包含自己摸索问题的临时解决方法)。看来,在CSDN上写一篇原创文章也是不容易的。
本文中涉及到关闭回调(onClose
)时出现了两个问题,虽然我都临时处理了,但还是需要原作者回应后再找到相应的解决办法才行,这样风险才最小。所以建议使用notistack做消息条的读者先暂时不要使用回调功能。期望有的读者能深入研究一下,找到更好的解决办法。
本文中介绍的消息条功能也可以和我的一篇《从零开始构建React下的多语言实现》中介绍的多语言功能集成在一起,也就是在一个工程中将这两者都应用。有兴趣的读者可以试一下。
修改后的gitee上的代码为:
欢迎留言指出错误或者提出宝贵改进意见。
转载地址:http://ltpyk.baihongyu.com/