关于在AEM项目中同时结合React Web组件与AEM组件使用Redux的可行性及实现咨询
嘿,看起来你已经在AEM项目里搭好了Redux的基础结构,还单独维护了一个React组件包,我来聊聊这种方案的可行性,以及具体怎么落地~
首先明确说:完全可行!AEM本身就支持整合React这类前端框架,你把状态管理(Redux)和UI组件拆分到不同包的思路其实很合理,能很好地实现逻辑与视图的解耦,也方便后续各自迭代维护。
下面是具体的实现步骤和注意事项:
一、让React组件包能访问到Redux Store
你的ui.frontend里已经配置好了Redux Store,但它和ui.frontend.react是两个独立的包,得先让React组件能拿到这个Store。这里推荐两种方式:
方式1:把Store暴露到全局(适合AEM前端打包场景)
在ui.frontend/src/app/site/utilities/store.js里,导出Store的同时把它挂载到window对象上,这样React组件初始化时就能直接获取:
import { configureStore } from '@reduxjs/toolkit'; const initialState = { count: 0 }; function countReducer(state = initialState, action) { const counter = state.count; if (action.type === 'INCREMENT') { return { ...state, count: counter + 1 }; } else if (action.type === 'DECREMENT') { return {...state, count: counter - 1}; } else { return state; } } const store = configureStore({ reducer: { count: countReducer } }); // 挂载到全局,供React组件访问 window.__REDUX_STORE__ = store; export default store;
方式2:将ui.frontend作为依赖引入(适合npm包管理场景)
如果你用npm workspace或者yarn workspace管理项目,可以把ui.frontend设为ui.frontend.react的依赖,然后直接在React组件里import Store:
# 在ui.frontend.react目录下执行 npm install ../ui.frontend
然后在React代码里:
import store from 'ui.frontend/src/app/site/utilities/store';
二、在React组件中接入Redux
首先确保ui.frontend.react里安装了react-redux:
npm install react-redux
1. 用Provider包裹根组件
在React的入口文件(比如src/index.js)里,把全局的Store传入Provider,这样所有子组件都能共享Redux状态:
import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import './index.css'; import App from './App'; // 获取全局的Redux Store(如果是方式2就直接import) const store = window.__REDUX_STORE__; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <React.StrictMode> <Provider store={store}> <App /> </Provider> </React.StrictMode> );
2. 在自定义组件中使用Redux状态
以你的custom-button组件为例,用useSelector获取状态,useDispatch触发动作:
import React from 'react'; import { useSelector, useDispatch } from 'react-redux'; const CustomButton = () => { // 从Store中获取count状态 const count = useSelector(state => state.count.count); const dispatch = useDispatch(); return ( <div> <p>当前计数: {count}</p> <button onClick={() => dispatch({ type: 'INCREMENT' })}>加1</button> <button onClick={() => dispatch({ type: 'DECREMENT' })}>减1</button> </div> ); }; export default CustomButton;
三、在AEM组件中正确加载资源
在AEM的ui.apps组件HTL文件里,要先加载包含Redux Store的前端资源,再加载React组件资源,确保React初始化时Store已经存在:
<sly data-sly-use.clientlib="/libs/granite/sightly/templates/clientlib.html"> <!-- 先加载ui.frontend的clientlib(包含Redux Store) --> <sly data-sly-call="${clientlib.js @ categories='your-project.ui.frontend'}"/> <!-- 再加载ui.frontend.react的clientlib(包含React组件) --> <sly data-sly-call="${clientlib.js @ categories='your-project.ui.frontend.react'}"/> </sly> <!-- React组件挂载的容器 --> <div id="react-root"></div>
四、一些优化和注意事项
简化Reducer写法:建议用Redux Toolkit的
createSlice来替代手动写reducer,代码更简洁易维护,还能避免手动处理状态不可变的问题:import { createSlice } from '@reduxjs/toolkit'; const initialState = { count: 0 }; const countSlice = createSlice({ name: 'count', initialState, reducers: { increment: (state) => { state.count += 1; }, decrement: (state) => { state.count -= 1; } } }); // 导出action creators export const { increment, decrement } = countSlice.actions; export default countSlice.reducer;之后dispatch的时候就可以直接用
dispatch(increment()),不用手动写action type啦。状态同步到AEM组件:如果AEM原生组件也需要和Redux状态交互,直接在AEM的JS里调用全局Store的方法就行:
// 获取当前状态 const currentCount = window.__REDUX_STORE__.getState().count.count; // 触发动作 window.__REDUX_STORE__.dispatch({ type: 'INCREMENT' });避免重复打包依赖:在两个前端包的webpack配置里,把
react、react-redux、@reduxjs/toolkit设为外部依赖,让它们共享全局实例,减小打包体积。
备注:内容来源于stack exchange,提问作者Stacy J

