React 状态管理器,我是这样选的

news/2024/5/20 12:28:53/文章来源:https://blog.csdn.net/web2022050901/article/details/127520877

前言

我们的前端团队在一直深度使用 React ,从最早的 CRA ,到后来切换到 umijs ,从 1.x、2.x、3.x 再到现在的 4.x,其中有一点不变的,就是我们一直在使用基于 react-redux 思想的 dva 作为状态管理工具。

在状态共享这方面,不像 VuexReact 的官方并没有强力推荐某种封装方案,所以 React 的状态管理工具五花八门,百花齐放。其中就有:

  • 做什么都要 dispatchredux 流派。包括:react-reduxdva、新星代表 zustand
  • 响应式流派 mobx。以及新星代表 valtio ,以及一个很有特点的库 resso
  • 原子状态流派。来自 facebook 开源的 recoil ,以及新星代表 jotai
  • 完全体 hooks 流派。hoxretoumijs@4 内置数据流,包括 Vue 官方推荐的新状态管理工具 pinia 也是这个流派。

更为重要的一点是,传统的 MVC 模式,建议我们将视图层的逻辑层分离。而在 dva 中,页面即视图,effects 则被我们用于业务逻辑的编写。在这种思想的影响下,不论是简单还是复杂的页面,我们都习惯去创建一个 dva-model,再加上 dispatch 都不是强依赖关系,久而久之, model 越来越臃肿,关系越来越难找,吐槽声越来越大。

随着技术不断发展,我们终归是要摆脱繁琐的 dva,寻找一个新的状态管理工具,来减少我们这一块的代码量,保护我们的秀发。

所以经过了一系列的试点,我也来介绍一下各流派的优缺点和我个人的倾向。

我们需要什么样的状态管理工具

可能不需要?

我们阅读一些状态管理工具的文档时候,可能就会先被这样一篇文章甩在脸上:《你可能不需要状态管理工具》。

是的,不管是用起来繁琐的 dva 还是更为简洁的 recoil ,我们都不应该滥用状态管理工具。滥用只会给我们后面的维护和重构带来麻烦。

什么时候需要?

状态管理工具的作用,就是状态的共享,当共享状态发生变化,所有使用方都会触发重新渲染。所以,当然是状态需要被多方共享的时候,才需要使用状态管理工具了。比如:

  • 当前登录的用户信息,姓名,角色,所属组织等
  • 静态数据字典的缓存
  • 需要 keep-alive 的数据(不一定用)
  • 页面功能复杂,模块化后,模块之间仍需要共享的数据

请不要在【基础组件】中使用共享的状态,【基础组件】应该保持自身的独立性,做到高内聚

对状态管理工具的要求?

随着项目经验的积累,我总结出了状态管理工具应该满足的几个特性:

1.共享状态(基础),能够满足上面列举的几个场景;
2.共享业务逻辑,比如修改个人密码后需要退出并跳转至登录页(在菜单栏和个人中心都要调用,相同的逻辑不应该写多次);
3.共享状态模块化,即按不同业务逻辑,分开不同的文件创建共享状态。
4.再复杂一些的,涉及到共享状态之间的依赖,比如当我修改当前登录人的角色之后(比如从”项目经理“切换至”系统管理员“),记录菜单权限的状态也需要更新。
5.使用时,有清晰的依赖来源(import from)。
6.对 TypeScript 支持良好,易编写。

主流状态管理工具都是怎么做的

传统流派 dva

对比 dvaVuex 不能说是非常相似,只能说是一模一样了。

  • dvastateVuexstate,用于存放需要共享的状态。
  • dvareducersVuexmutations,用于编写修改共享状态的方法。
  • dvaeffectsVuexactions,用于编写存在副作用的方法,比如处理异步业务逻辑。
  • dva 使用 namespace 属性标记子模块的名称,Vuex 使用 modules 属性,拆分子模块。

优点

  • 在当时没有 hooks API 的环境下,算是一套不错的整合方案,能够满足绝大多数的共享业务场景。
  • 深度整合 redux redux-saga ,便于 redux 用户能够快速切换。

缺点

  • 使用时没有清晰的来源 dva 使用 hoc connect 的方式,将 store 中的属性注入到组件的 props 上。如下图:以 JS 的角度来看,products 的来源和类型都是非常不清晰的。
  • TypeScript 支持不友好 没有清晰的依赖关系,类型支持自然也是很差的。
  • 不能满足共享状态之间的依赖 比如我修改了当前用户角色,需要根据角色权限重新查询可访问的菜单。但我不能在菜单store中监听,只能在用户store主动触发菜单查询,或单独写一个组件,利用 componentDidUpdate 监听 用户store 再发起请求。

zustand

拥有 22K stars 的 zustand 则是非常值得尝试的传统派替代品。

  • 它面向 hooks API,一个 store 就是一个 hooks
  • 它更简洁,直接将 state / reducers / effects 平铺开来,functioneffects / reducers ,其它都是 state
  • import 来源清晰,对 TypeScript 的支持也更友好
  • 提供了 subscribe 接口,可在组件外监听状态变化,以实现状态依赖。

基本把上述的缺点都解决了。

响应式流派 mobx

mobx 的出现给 redux 带来了很大的冲击。

通过一个装饰器(observer / observable),就能使普通的组件能够监听变量的变化而渲染,完全抛弃掉 state 。不仅如此,mobx 提供了 computed 等一些好东西,使 React 也能使用到 Vue 组件的特性。而从 Vue 转过来学习 React 的同学,都会对 mobx 拍手称赞。

在体验过 mobx 的爽快之后,当时团队中有部分声音,是希望以后抛弃掉 state,转而全面使用 mobx 作为组件状态。是啊,一套方案,解决内部状态和共享状态两个问题,何乐而不为呢。

缺憾

很快,这种 mobx 为王的声音很快又消失了,因为…

  • 响应式 API 和 React 水土不服,React 就是需要 setState 来修改状态,现在你 state.value++ 就改了,是要造反么?新来的同学学完 React 基础,结果和他说,那些用不上,再学学 mobx 响应式吧,新同学是否会心中充满问号?
  • 会有一些隐蔽的坑,比如往 observable 添加属性时,不能直接添加,而要通过 extendObservable ,我们有很多同学踩了这坑。
  • 基础组件如果也使用 mobx 则违反了高内聚的原则,不使用,两边风格又不统一。你见过哪个组件库需要附带一套状态管理工具的?
  • 响应式其实就是基于 Proxy 实现的,我明明希望传递的是一个数组,但拿到的却是一个 Proxy。强迫症实在受不了。

所以,mobx 很优秀,但我实在爱不来。

原子状态流派 recoil

体验过 recoil 之后,我能感受到,recoil 是希望你在使用全局状态时,和 useState 的体验完全一致。是的,useRecoilStateuseState 的使用方式几乎是完全一样的,只不过 recoil 的默认值需要使用 atom 包裹一下罢了。于是你的 atom 状态就实现全局共享了。

为了解决共享状态依赖的需求,recoil 还很贴心地提供了 selector API,用于实现共享状态的拆分和依赖,你把它当作 useMemo 或者计算属性来看待就可以了。(当然 selector 还有支持写入(set)以及异步处理,但我还没找到必须要用它的场景)

不足

recoil 理念真的很简单,就是以 useState 的习惯实现状态共享。所以在业务逻辑共享这一块,它似乎没有给出很好的方案。但是既然已经是面向 hooks API 了,自定义 hooks 本身就可以实现业务逻辑的复用了。比如下面这段伪代码:

// src/hooks/useChangePassword.js// 修改密码动作
export function useChangePassword(){// 当前用户信息的共享状态const [userInfo, setUserInfo] = useRecoilState(userAtom);// 修改密码const changePassword = async (oldPassword, newPassword) => {// 1. 调用修改密码接口const result = await post('/api/password', { oldPassword, newPassword });if(reuslt.success) {// 2. 清空当前用户信息setUserInfo(null);// 3. 跳转至登录页history.push('/login');// 4. 提示信息message.warn('已修改密码,请重新登录');} else {// 操作失败提示message.error(result.errMsg);}}return changePassword;
} 

这样,在个人中心菜单栏密码过期的几个场景中,我都可以这一段 hooks 实现修改密码后的系列动作,而不是每个地方都调用一次接口。

jotai 几乎是完全对标 recoil 的,我就不赘述了

hooks 完全体 – hox

hox 刚出来不久,我就关注到了,并觉得其思想非常棒。但翻阅了一下源码后发现,它依赖了一个实验性的渲染器 react-reconciler,以至于我不敢将它用于生产环境。直到 react@18.x 带来了一个新的 hooks: useSyncExternalStore ,以及基于它实现的 hox@2.x

我们来看它的介绍:

在 hox 中,任意的自定义 Hook,经过 createStore 包装后,就变成了持久化,可以在组件间进行共享的状态。

我的天,真的太神奇了,你只要用 createStore 包裹你写的某个 hooks ,它里面的状态就变成了可共享的了。

实现原理

举个简单的例子,我写了一个自定义 hooks useMyHook:

export const useMyHook = () => {const [value, setValue] = useState(1);return [value, setValue];
} 

我在 组件 A组件 B 中都使用了它。正常情况下,A 和 B 中的 value 当然是不同的。

但是假如我“偷偷地”将 hooks 放在最外面执行,比如 App 下,然后再用 Context 传递下去:

// App.tsx
export const Context = React.createContext({});export default function App() {// 在最外层执行 hooksconst myHookResult = useMyHook();// 通过 Context 向下传递return (<Context.Provider value={myHookResult}>{children}</ContextProvider>)
} 

我再给你一个封装后的 Hook:

// 在 组件A 和 组件B 中使用这个 hooks
export const useMyHookWrapped = () => {// 从外部获取到 useMyHook 的内容const myHookResult = useContext(Context);return myHookResult;
} 

综上,你就会发现,相当于 useMyHook 只被使用了一次,其它需要用到的地方,都是使用 useContext 获取的。那么自然就实现了状态共享了。而这些,都是 createStore 实现的。

并且,和状态相关的业务逻辑,也写在了同一个 hooks,还可以不受限制地获得完整的 hooks 体验,使用第三方 hooks 库。最令人惊叹的是,由于都是 hooks API,你可以先在组件中编写业务逻辑,当发现逻辑需要共享时,直接复制抽离出去;或者是当你需要迁移部分功能到另外的项目,不需要共享了,只需要去掉 createStore ,它就又变成了普通 hooks 了。

umijs@4.x 数据流方案

umijs@4.x 正式推出后,我注意到它内置了一套和 hox 一模一样的数据流方案。我大概翻了一下,虽然它不是直接引用的 hox ,但内部实现逻辑如出一辙。

它的特点是,采用约定式目录结构,不用专门写 createStore ,而是自动帮我们引入了所有 model 目录下的 hooks,并注册。在页面中则是通过统一的 useModel,通过其自动生成的 namespace 引用,比如 useModel('product')。但这也导致了依赖不明确的问题,umi4 还特地通过编写插件的方式解决跳转问题。

但也正是它的约定式,产生了一些让我觉得膈应的地方:

1.useModel('product') 必须要通过装插件才能点击跳转。
2.必须要在 umijs@4.x 体系下才能使用,无法快速复制迁移到其它的框架下使用。
3.插件偷偷帮你实现了 createStore,乍一看和普通的 hooks 完全一样,其实已经持久化了。对新人学习很不友好。(你写个 createStore 他还知道有不一样的地方,回去查。umijs@4.x 没看到文档根本不知道有这回事)

我的选择

对几个工具的主观评价(满分5)

场景dvazustandmobxrecoil/jotaihoxumijs@4.x
状态共享支持支持支持支持支持支持
业务逻辑共享effectsfunction不提供不提供hooks 自由实现hooks 自由实现
状态拆分/模块化namespace独立 store支持任意属性、对象独立 atom独立 store独立 store
共享状态依赖不支持自定义 useStore 实现,或 subscribe,不支持 create 时的 gettercomputedselectorhooks 直接依赖即可需要通过 useModel 依赖
使用时的依赖清晰不支持支持支持支持支持不支持
对 TypeScript 友好⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
易用性⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐(5分给atom,2分给selector)⭐⭐⭐⭐⭐⭐⭐⭐⭐
和 React 亲和程度⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

综上,你可以发现,hox 的方案算是所有工具中最好的,能够满足所有的场景,并获得不错的评分。我也会在今后的工作中更深入地使用 hox 方案,发掘其是否还有更优秀的用法或隐藏的问题。

最后也欢迎各位一同讨论你们在状态管理工具上的取舍。

PS: 相同的支持者 —— Pinia

Vue 已经将默认的状态管理工具切换为了 Pinia。它的最大特点就是专注于提供 组合式API 风格的API(其实就是 Reacthooks 风格)。

特别是在 store 声明这一块,虽然 Pinia 还是从 Vuex 的风格开始介绍的,但是看到了它 组合式API 风格的 demo,我悟了:

ref 换成 useState ,这和 hox 有啥区别?Pinia 的案例也让我对 hox 有了更大的信心。是的,连隔壁 Vue 也推崇这种风格的状态共享方案。

关于 Vue 和 React 对比的一些感想: 我记得以前看过一些文章,大概意思就是说 Vue3.x 的组合式API 抄袭 React 的 hooks 之类的,社区吵的也很厉害,也有人开始反驳说 hooks 和 组合式API 根本不是一个东西,setup不会每次都执行之类的。 确实,他们肯定不是同一个东西,毕竟两个框架的渲染机制都是不一样的,但他们更多的在于思想的借鉴。hooks 风格有很多好处,功能结构清晰,数据来源明确等,其它框架参考了这种风格,结合自身实现了一套方案,达到了相同的目的,当我们后续需要使用更多其它框架的时候,能够很自然地映射过去,难道不是更好么。

PS:为什么不选

zustand

zustand 很优秀,但使用它仍然会让我联想到在 effects 中使用 dispatch 更新状态的痛苦时光。是啊,在 zustand 你还是要使用 set 来修改状态。虽然它也提供了额外的 setState 方法让你直接更新状态,但是风格和 useState 差得远啊,让我觉得不伦不类。

mobx / valtio

这两个很明显了,响应式的风格,跟 React 对着干,我当然是不选的。

recoil / jotai

我原本是比较倾向于 recoil 的,奈何 selector 的用法有一定的上手成本,让新手学完 useMemo 就能运用上不好么。

细心点你会发现,zustand / valtio / jotai 是同一个开源团队的作品,而且他们的名字分别是 德语、芬兰语、日语 的状态。所以没有选择某个,并不是说这些工具不好,他们之间没有孰优孰劣,只是面向风格不一样罢了。

redux-toolkit

因为长期接触 dva 并饱受其糟粕,比较少关注 redux 相关的内容了。

我简单阅读了下文档,说一下我的想法。

看了下发布记录,近两年出来的 redux-toolkit 应该是利用 createSlice 补足 redux 本身在 状态拆分/模块化 这块的不足,以及解决 dispatch({ type:'xxx' }) 这种依赖关系不清晰的设计缺陷。

从 Demo 来看,整体风格还是借鉴的 Vuex / dva,但允许将 reducers 的方法单独提取出来,以供单独业务逻辑封装(比如 Demo 中的 incrementAsync

但我还是想吐槽的是

1.既然 createSlice 已经是对 reducers 的封装,那么我直接执行 actions 的时候,直接调用不就好了,为什么还要套一层 dispatch(incrementByAmount(amount))? 是为了延续 dispatch 的坏习惯么? 
2.既然 createSelector 是为了更好地做模块拆分,为何不像 zustand 一样提供专门的 hooks 用于获取状态,而是仍要通过 useSelector 再写一遍 selectCount? 
3.内置了 immer。我觉得 immer 是一个非常牛x 的库,我在处理树状结构的数据时,用得非常爽。但我非常不建议将 immer 作为一个默认特性内置在 reducers 中。 immer 的特性会导致新人对引用类型的认知产生误解:为什么我在 reducers 中直接修改 state 不会影响原状态,在外面就会?

immer 本身设计的是没问题的,使用的时候必须用 produce 包裹,并且内部状态都命名为 draftXXX,告诉我们在 produce 环境下的变量和外面是不一样的。  我认为,immer 应该由使用者根据自身的数据复杂度在代码中主动显示地使用(自己写 produce 创建环境)。工具库内部,你可以使用,但不应该将 immer 环境暴露给使用者。

总的来说,redux-toolkit 应该只是一个 redux 体系的填坑作品,确实弥补了 redux 在业务开发上的不足,但比起其它库,还是无法打动我。redux 的核心概念虽然简单,但仍需要有额外的学习成本,各种 redux 生态工具的学习成本就更高了。但 hox 的学习成本,在你会了 hooks 之后,是 0 啊😊。

最后

最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。



有需要的小伙伴,可以点击下方卡片领取,无偿分享

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_218366.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

(附源码)计算机毕业设计SSM跨移动平台的新闻阅读应用

&#xff08;附源码&#xff09;计算机毕业设计SSM跨移动平台的新闻阅读应用 项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目…

DM-DM DBLINK使用配置

简单介绍 DM-DM DBLINK支持3种连接方式创建&#xff0c;分别是&#xff1a;dmmal、dpi、odbc。 其中dpi、odbc属于第三方接口&#xff0c;dmmal属于原生接口。dpi类型dblink为新版本新添加支持&#xff0c;以前版本中不支持。 环境说明 &#xff08;1&#xff09;数据库版本…

2023届C/C++软件开发工程师校招面试常问知识点复盘Part 7

目录46、C类的成员变量初始化顺序及拓展47、强制转换类型操作符号48、const 成员函数–常成员函数与常量对象49、volatile关键字50、赫夫曼树51、前缀树46、C类的成员变量初始化顺序及拓展 注意&#xff1a; 1、const成员或者引用必须在成员变量初始化列表中初始化&#xff0c;…

git的基础指令操作

git的下载地址&#xff1a;https://git-scm.com/download 安装好git后 在桌面上右键即可以看到两个git的快捷方式。 需要先对git进行基本的配置&#xff0c;即需要配置用户名和用户邮箱 1. 打开Git Bash 2. 设置用户信息 git confifig --global user.name “zqy” git confi…

权限项目 1_搭建环境

硅谷通用权限系统&#xff1a;搭建环境 一、项目介绍 1、介绍 权限管理是所有后台系统都会涉及的一个重要组成部分&#xff0c;而权限管理的核心流程是相似的&#xff0c;如果每个后台单独开发一套权限管理系统&#xff0c;就是重复造轮子&#xff0c;是人力的极大浪费&…

第 1 章之:二叉树特性

声明&#xff1a;文章为博主原创&#xff0c;转载请联系博主。文章若有错误和疏漏之处&#xff0c;还望大家不吝赐教&#xff01; 第一章&#xff1a;数据结构与算法基础--------------------------- 本章重点内容为&#xff1…

基于麻雀算法二维oust图像分割算法研究附matlab代码

✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法 …

期刊|认知科学领域期刊《Trends in Cognitive Sciences》

Hello&#xff0c;大家好&#xff01; 这里是壹脑云科研圈&#xff0c;我是Ns&#xff5e; 今天我们介绍的是爱思维尔(Elsevier)旗下细胞出版社&#xff08;cell press&#xff09;发行的关于认知科学领域的期刊&#xff1a;Trends in Cognitive Sciences。 1 期刊简介 基本…

mysql之给字符串加索引

文章目录前言长字段加索引前缀索引对覆盖索引的影响合理的使用前缀索引总结前言 之前的文章介绍了主键索引和唯一索引的区别&#xff0c;也介绍了主键索引和唯一索引在不同业务场景下的区别。今天我们继续介绍&#xff0c;普通索引怎么合理的使用。 长字段加索引 这里我们就…

Spring6.0全新发布,快来看看

Spring6.0全新发布&#xff0c;快来看看 Spring Framework 6.0 发布了首个 RC 版本。 翻译后页面(有点好笑)&#xff1a; On behalf of the team and everyone who has contributed, I am pleased to announce that Spring Framework is available now.6.0.0-RC2 Spring Frame…

零信任如何给为企业的数字资源保驾护航?

零信任安全最早由著名研究机构Forrester的首席分析师约翰.金德维格在2010年提出。 零信任安全针对传统边界安全架构思想进行了重新评估和审视&#xff0c;并对安全架构思路给出了新的建议。 零信任模型是什么 零信任是一种基于严格身份验证的网络安全架构。、 在该架构下&am…

【SpringBoot笔记12】SpringBoot框架实现文件上传和文件下载

这篇文章&#xff0c;主要介绍如何使用SpringBoot框架实现文件上传和文件下载。 目录 一、SpringBoot文件上传 1.1、引入依赖 1.2、编写文件上传页面 1.3、编写文件上传代码 &#xff08;1&#xff09;MultipartFile对象 &#xff08;2&#xff09;ResourceUtils工具类 …

音频拼接在一起怎么做?这篇文章来告诉你

随着互联网的发展&#xff0c;很多优质歌曲都纷纷地呈现在大家眼前&#xff0c;而将不同的音乐合并在一起&#xff0c;并且放入视频里&#xff0c;也是别有一番风味&#xff0c;那么许多人会好奇音频如何拼接在一起呢?下面就为大家分享两个好用的方法&#xff0c;只要一点时间…

【C++】使用对象自动管理指针(用到运算符重载)

文章目录1. 首先设计整型类&#xff1a;class Int普通指针2. 设计一个Object类&#xff0c;并设计Int类型的指针。那如何获取Int类型的值呢&#xff1f;1. 首先设计整型类&#xff1a;class Int class Int { private:int value; public:Int(int x 0) :value(x){cout <<…

Springbootg整合validation整合

坚持年年写博客&#xff0c;不能断了&#xff0c;所以粘贴平时写的一份笔记吧 一、简介 校验参数在以前基本都是使用大量的if/else&#xff0c;稍微方便一点的可以使用反射自定义注解的形式&#xff0c;但是复用性不是很好&#xff0c;并且每个人对于的自定义注解有着自己的使…

Java基础-任务执行服务

今天小编带领大家一起来探索Java中的任务执行服务 关于任务执行服务&#xff0c;我们介绍了&#xff1a; 任务执行服务的基本概念。 主要实现方式&#xff1a;线程池。 定时任务。 &#xff08;1&#xff09;基本概念 任务执行服务大大简化了执行异步任务所需的开发&…

算法 - 最少交换次数来组合所有的 1 II

目录 题目来源 题目描述 示例 提示 题目解析 算法源码 题目来源 2134. 最少交换次数来组合所有的 1 II - 力扣&#xff08;LeetCode&#xff09; 题目描述 交换定义为选中一个数组中的两个 互不相同 的位置并交换二者的值。 环形数组是一个数组&#xff0c;可以认为 第…

第五章:乱序执行

1.概念 指令在执行时常常因为一些限制而等待。例如&#xff0c;MEM单元访问的数据不在cache中,需要从外部存储器中取&#xff0c;这个过程通常需要几十、几百个Cycle&#xff0c;如果是顺序执行的内核,后面的指令都要等待&#xff0c;而如果处理器足够智能&#xff0c;就可以先…

修改数组(秋季每日一题 31)

给定一个长度为 nnn 的正整数数组 a1,a2,…,ana_1,a_2,…,a_na1​,a2​,…,an​。 你可以任意改变其中任意元素的值。 但是&#xff0c;改变后的元素的值仍需是正整数。 将一个元素的值从 aaa 变为 bbb 所需要付出的代价为 ∣a−b∣|a−b|∣a−b∣。 对于一个正整数 ttt&am…

Elasticsearch 查询详解

1 数据准备 PUT student_index {"settings": {"number_of_shards": 1,"number_of_replicas": 0},"mappings": {"properties": {"birthday": {"type": "date","format": "yyy…