ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

在 Gatsby 站点中集成 Redux Store:wrapRootElement 双端注入实战指南

在 Gatsby 站点中集成 Redux Store:wrapRootElement 双端注入实战指南 在 Gatsby 站点中集成 Redux StorewrapRootElement 双端注入实战指南【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby本文基于 Gatsby 官方文档 Adding a Redux Store 展开结合仓库内 using-redux 示例站点 与 Gatsby 源码中的 API 定义系统讲解如何为 Gatsby 站点接入 Redux 做自定义状态管理包括 store 的创建方式、双端SSR 与浏览器注入原理、Provider 封装技巧以及跨页面共享状态的完整可运行示例。读完后你将能独立为自己的 Gatsby 项目配置一套可服务端渲染、可浏览器交互的 Redux 状态层。为什么 Gatsby 项目需要自定义 Redux StoreRedux 的核心价值在于帮助开发者写出行为一致的应用它能在不同环境客户端、服务端、原生端中稳定运行并且天然易于测试。正因如此Gatsby 自身也把 Redux 作为底层核心技术之一使用。但需要注意一个关键区别Gatsby 内部使用的 Redux 是框架自身的状态管理管理页面数据、插件状态等它与用户业务代码的状态是隔离的。如果你想用 Redux 管理自己的业务状态例如用户登录态、购物车、计数器等就必须创建属于你自己的 Redux store并通过 Gatsby 的扩展点把它注入到应用根节点中。从源码注释可以确认这一点——在 api-browser-docs.ts 中wrapRootElement被明确定义为「用于包裹根元素适合设置包裹整个应用的 Context Provider」官方给出的示例正是react-redux的Providerexports.wrapRootElement ({ element }) { return ( Provider store{store} {element} /Provider ) }需要挂钩的两个扩展点要在 Gatsby 站点中使用 Redux 做自定义状态管理需要同时挂钩 Gatsby 的两个扩展点wrapRootElementSSR API在 Gatsby 的服务端渲染过程中执行位于 gatsby-ssr.js。wrapRootElementBrowser API属于 Gatsby 的浏览器端 API位于 gatsby-browser.js。两个文件中的导出写法完全一致都是把统一的 provider 包装函数导出// gatsby-ssr.js import wrapWithProvider from ./wrap-with-provider export const wrapRootElement wrapWithProvider // gatsby-browser.js import wrapWithProvider from ./wrap-with-provider export const wrapRootElement wrapWithProvider为什么必须两端都挂钩因为 Gatsby 是静态站点生成器构建时gatsby build会在服务端预渲染 HTML运行时浏览器又要负责客户端路由切换与交互。如果只在gatsby-ssr.js中注入 Provider浏览器端水合hydration时状态结构不一致如果只在gatsby-browser.js中注入则服务端渲染出的 HTML 中不包含 Provider 包裹。两处同时挂钩才能保证服务端渲染与浏览器端渲染的行为保持一致——这正是 Redux 所倡导的行为一致在 Gatsby 中的落地方式。源码层面api-browser-docs.ts 明确注释道浏览器端存在与 SSR API 等价的 hook并推荐两个 API 一起使用其官方示例正是 using-redux 这个示例站点。创建自定义 Redux Storereducer 与初始状态自定义 store 的核心是一个 reducer 函数和一个初始状态。在 using-redux 示例 中store 被封装为一个工厂函数import { createStore as reduxCreateStore } from redux const reducer (state, action) { if (action.type INCREMENT) { return Object.assign({}, state, { count: state.count 1, }) } return state } const initialState { count: 0 } const createStore () reduxCreateStore(reducer, initialState) export default createStore这段代码展示了几个关键点reducer 必须是纯函数根据action.type返回新的 state且不直接修改原 state这里使用Object.assign({}, state, ...)生成新对象初始状态集中定义initialState单独声明便于理解 store 的初始形状工厂函数模式不直接导出一个 store 实例而是导出createStore函数每次调用都创建一个全新的 store。关于最后一个模式在wrapRootElement处理器内部实例化 store 有一个重要原因见 wrap-with-provider.js 中的注释服务端渲染时每个页面都会重新执行wrapRootElement因此每次都需要一个全新的 store避免页面之间状态互相污染浏览器端React 挂载时该函数只会被调用一次因此 store 在整个应用生命周期内保持单例。编写共享的 Provider 包装组件为避免在gatsby-ssr.js与gatsby-browser.js中重复书写 Provider 逻辑示例将其抽取为独立的 wrap-with-provider.jsimport React from react import { Provider } from react-redux import createStore from ./src/state/createStore // eslint-disable-next-line react/display-name,react/prop-types export default ({ element }) { // Instantiating store in wrapRootElement handler ensures: // - there is fresh store for each SSR page // - it will be called only once in browser, when React mounts const store createStore() return Provider store{store}{element}/Provider }该组件接收 Gatsby 传入的{ element }即 Gatsby 构建出的根 React Element用Provider store{store}将其包裹后返回。wrapRootElement的返回值类型是ReactNode——被包裹后的元素——这一契约同样在 api-browser-docs.ts 的returns {ReactNode} Wrapped element注释中得到印证。在组件中连接 Redux 状态完成注入后任意组件都可以通过react-redux的connect高阶组件读取状态并派发 action。using-redux 示例 中的计数器组件展示了完整用法import React from react import PropTypes from prop-types import { Link } from gatsby import { connect } from react-redux const Counter ({ count, increment }) ( div pCount: {count}/p button onClick{increment}Increment/button /div ) Counter.propTypes { count: PropTypes.number.isRequired, increment: PropTypes.func.isRequired, } const mapStateToProps ({ count }) { return { count } } const mapDispatchToProps dispatch { return { increment: () dispatch({ type: INCREMENT }) } } const ConnectedCounter connect(mapStateToProps, mapDispatchToProps)(Counter)这里可以看到 Redux 在 Gatsby 中的典型数据流mapStateToProps从全局 state 中取出count映射为组件 propmapDispatchToProps将dispatch包装成increment回调派发INCREMENTactionconnect把两者注入Counter得到ConnectedCounter。由于 Provider 包裹在根元素层面因此这个ConnectedCounter可以被放入布局组件而布局组件会被所有页面共享。示例站点的 layout.js 将其放在导航区域站点内的三个页面 index.js、a.js、b.js 都基于该布局渲染——也就是说Redux 状态天然实现了跨页面共享在任意页面点击 Increment 后切换到其他页面计数仍然保持这正是应用级状态管理在 Gatsby 多页场景下的核心价值。完整项目结构与运行方式一个完整的 Gatsby Redux 项目结构如下与 using-redux 示例一致├── gatsby-browser.js # 浏览器端 wrapRootElement ├── gatsby-ssr.js # 服务端渲染端 wrapRootElement ├── wrap-with-provider.js # 共享的 Provider 包装组件 ├── gatsby-config.js # 站点配置 ├── package.json # 依赖与脚本 └── src/ ├── components/ │ └── layout.js # 布局组件含 connect 计数器 ├── pages/ │ ├── index.js │ ├── a.js │ ├── b.js │ └── c.js └── state/ └── createStore.js # store 工厂函数所需的核心依赖见 package.jsongatsby、react、react-dom、react-redux与redux。示例站点支持以下脚本gatsby develop # 开发模式热更新 gatsby build # 生产构建含服务端预渲染 gatsby serve # 本地预览生产构建产物在开发模式中配合 Redux DevTools 即可获得实时编辑代码 时间旅行调试time-traveling debugger的体验——这也是 Redux 官方主打的开发者体验优势。与 wrapPageElement 的区别及最佳实践在 api-browser-docs.ts 中Gatsby 还提供了另一个类似的 APIwrapPageElement。二者容易混淆官方注释给出了清晰的边界API包裹粒度生命周期适用场景wrapRootElement根元素页面切换时不会卸载设置 Context Provider如 ReduxProviderwrapPageElement页面元素页面切换时会重新挂载包裹页面级布局、持久 UI 元素因此集成 Redux 时应选择wrapRootElement——Provider 必须在应用根节点上保持常驻不能随页面切换而卸载。同时官方建议若同时需要页面级布局包裹两个 API 应配合使用而不是用wrapPageElement替代wrapRootElement。小结在 Gatsby 中集成自定义 Redux store 的完整路径可以概括为四条创建 store 工厂函数定义 reducer 与初始状态导出createStore()编写共享 Provider 组件在wrapRootElement处理器内部实例化 storeSSR 每次新建、浏览器仅一次并用Provider包裹element双端挂钩在 gatsby-ssr.js 与 gatsby-browser.js 中同时导出wrapRootElement保证服务端渲染与浏览器行为一致组件消费状态通过react-redux的connect实现mapStateToProps/mapDispatchToProps实现跨页面共享的应用级状态。这套方案不依赖任何第三方插件完全基于 Gatsby 官方的扩展点 API 实现并且由于 Gatsby 自身也使用 Redux 作为底层技术这种自定义状态层 框架状态层并存的方式与 Gatsby 的架构天然契合可放心用于生产项目。想进一步深入可继续阅读仓库中的 using-redux 示例源码 与官方文档 Adding a Redux Store或参考 Redux 官方入门指南了解 reducer、action 与 store 的更多设计模式。【免费下载链接】gatsbyReact-based framework with performance, scalability, and security built in.项目地址: https://gitcode.com/gh_mirrors/ga/gatsby创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表