区块链钱包开发(二十)—— 前端框架和页面
源码: https://github.com/MetaMask/metamask-extension/tree/main/uiMetaMask UI端采用了分层架构设计,每一层都有明确的职责:ControllersServicesUtilssubmitRequestToBackgroundonNotificationsetBackgroundConnectionRedux StoreRoot Re
·
源码: https://github.com/MetaMask/metamask-extension/tree/main/ui
0.整体架构
MetaMask UI端采用了分层架构设计,每一层都有明确的职责:
- UI层:负责用户界面的展示和交互
- Hooks层:封装业务逻辑和状态管理
- Selectors层:提供高效的数据访问和派生
- Ducks层:模块化的状态管理,每个功能域独立
- Store层:统一的Redux状态管理中心
- Background Connection:UI与后台的通信桥梁
- 后台脚本:核心业务逻辑和数据处理
1.UI → Background 交互机制
核心通信桥梁:background-connection.ts
// metamask-extension/ui/store/background-connection.ts
export const callBackgroundMethod = <R>(
method: string, // Background 方法名
args: unknown[], // 参数数组
callback: (error: Error | null, result?: R) => void // 回调函数
): void => {
// 通过 postMessage 与 Background 通信
const message = {
id: generateId(),
method,
args,
origin: window.location.origin,
};
// 发送到 Background
window.postMessage(message, '*');
// 监听 Background 的响应
window.addEventListener('message', handleResponse);
};
通信流程
UI 组件 → callBackgroundMethod → postMessage → Background Service Worker
↓
Background 处理请求 → 调用对应 Controller → 返回结果
↓
Background → postMessage → UI → 回调处理结果
实际调用示例
// 在 UI 中调用 Background 方法
callBackgroundMethod(
'addNewAccount', // Background 方法名
[], // 参数
(error, result) => { // 回调
if (error) {
console.error('添加账户失败:', error);
} else {
console.log('新账户地址:', result);
}
}
);
2. UI 核心模块详解
2.1 ui/store
- 状态管理中心
actions.ts
- 后台方法桥接
// metamask-extension/ui/store/actions.ts
export function addNewAccount() {
return (dispatch: MetaMaskReduxDispatch) => {
dispatch(showLoadingIndication()); // 显示加载状态
return new Promise<void>((resolve, reject) => {
// 调用 Background 方法
callBackgroundMethod('addNewAccount', [], (err) => {
if (err) {
dispatch(displayWarning(err)); // 显示错误
reject(err);
return;
}
dispatch(showAccountsPage()); // 跳转账户页
resolve();
});
})
.then(() => dispatch(hideLoadingIndication()))
.catch(() => dispatch(hideLoadingIndication()));
};
}
作用:
- 封装
callBackgroundMethod
调用 - 管理 UI 状态(加载、错误、成功)
- 提供 Redux Thunk 接口
store.ts
- Redux Store 配置
// metamask-extension/ui/store/store.ts
export function configureStore(initialState = {}) {
const store = createStore(
rootReducer,
initialState,
compose(
applyMiddleware(thunk, logger),
// 其他中间件...
)
);
return store;
}
作用:
- 配置 Redux Store
- 集成中间件(thunk、logger 等)
- 提供状态管理基础设施
2.2 ui/selectors
- 数据选择与派生
选择器的作用
// metamask-extension/ui/selectors/metamask-notifications/metamask-notifications.ts
export const getValidNotificationAccounts = createSelector(
[getMetamask], // 依赖选择器
(metamask): string[] => metamask.subscriptionAccountsSeen // 派生逻辑
);
// 使用 reselect 进行 memoization
export const getMetamaskNotificationsUnreadCount = createSelector(
[getMetamaskNotifications],
(notifications): number => {
return notifications
? notifications.filter((notification) => !notification.isRead).length
: 0;
},
);
作用:
- 数据投影:从复杂状态中提取所需数据
- 性能优化:memoization 避免不必要的重计算
- 数据转换:将原始状态转换为 UI 友好的格式
- 依赖管理:声明式地管理数据依赖关系
选择器层次结构
// 基础选择器
const getMetamask = (state: AppState) => state.metamask;
// 派生选择器
export const getMetamaskNotifications = createSelector(
[getMetamask],
(metamask): Notification[] => metamask.metamaskNotificationsList,
);
// 复合选择器
export const getUnreadNotifications = createSelector(
[getMetamaskNotifications],
(notifications) => notifications.filter(n => !n.isRead)
);
2.3 ui/hooks
- 业务逻辑封装
自定义 Hook 的作用
// metamask-extension/ui/hooks/useNotificationAccounts.ts
function useNotificationAccounts() {
// 使用选择器获取数据
const accountAddresses = useSelector(getValidNotificationAccounts);
const internalAccounts = useSelector(getInternalAccounts);
// 业务逻辑:地址映射为账户对象
const accounts = useMemo(() => {
return accountAddresses
.map((addr) => {
const account = internalAccounts.find(
(a) => a.address.toLowerCase() === addr.toLowerCase(),
);
return account;
})
.filter(<T,>(val: T | undefined): val is T => Boolean(val));
}, [accountAddresses, internalAccounts]);
return accounts;
}
作用:
- 逻辑复用:封装常用的业务逻辑
- 状态管理:管理组件内部状态
- 副作用处理:处理异步操作、事件订阅等
- 数据转换:将选择器数据转换为组件所需格式
Hook 与选择器的协作
// 选择器:纯数据获取
const getAccountBalance = createSelector(
[getSelectedAccount, getCurrentNetwork],
(account, network) => ({ account, network })
);
// Hook:业务逻辑 + 状态管理
function useAccountBalance() {
const { account, network } = useSelector(getAccountBalance);
const [balance, setBalance] = useState(null);
useEffect(() => {
if (account && network) {
fetchBalance(account.address, network.chainId)
.then(setBalance);
}
}, [account, network]);
return balance;
}
2.4 ui/ducks
- 功能模块化
Duck 模式的作用
// metamask-extension/ui/ducks/notifications/notifications.js
// 一个 Duck 包含:actions、reducers、selectors、types
// Actions
export const SHOW_NOTIFICATION = 'notifications/SHOW_NOTIFICATION';
export const HIDE_NOTIFICATION = 'notifications/HIDE_NOTIFICATION';
// Action Creators
export const showNotification = (message) => ({
type: SHOW_NOTIFICATION,
payload: message,
});
// Reducer
const initialState = { messages: [] };
export default function notificationsReducer(state = initialState, action) {
switch (action.type) {
case SHOW_NOTIFICATION:
return { ...state, messages: [...state.messages, action.payload] };
case HIDE_NOTIFICATION:
return { ...state, messages: state.messages.filter(m => m.id !== action.payload) };
default:
return state;
}
}
// Selectors
export const getNotifications = (state) => state.notifications.messages;
作用:
- 模块化:将相关功能组织在一起
- 可维护性:降低代码耦合度
- 可测试性:独立测试功能模块
- 可重用性:在不同页面间复用功能
3. 完整的数据流示例
场景:用户添加新账户
// 1. UI 组件触发
function AddAccountButton() {
const dispatch = useDispatch();
const handleClick = () => {
dispatch(addNewAccount()); // 派发 action
};
return <button onClick={handleClick}>添加账户</button>;
}
// 2. Action 处理
export function addNewAccount() {
return (dispatch) => {
dispatch(showLoadingIndication()); // 更新 UI 状态
return new Promise((resolve, reject) => {
// 调用 Background
callBackgroundMethod('addNewAccount', [], (err, result) => {
if (err) {
dispatch(displayWarning(err));
reject(err);
} else {
dispatch(showAccountsPage());
resolve(result);
}
});
});
};
}
// 3. Background 处理
// app/scripts/metamask-controller.js
async addNewAccount(accountCount, _keyringId) {
const addedAccountAddress = await this.keyringController.withKeyring(
{ type: KeyringTypes.hd, index: 0 },
async ({ keyring }) => {
const [newAddress] = await keyring.addAccounts(1);
return newAddress;
},
);
return addedAccountAddress;
}
// 4. 状态更新
// KeyringController 更新状态 → 触发 stateChange 事件
// UI 通过选择器获取新状态 → 重新渲染
4. 模块间的协作关系
5. 总结
MetaMask UI 架构的核心是:
ui/store
:状态管理中心,通过 actions 桥接 UI 与 Backgroundui/selectors
:数据选择与派生,提供 memoized 的数据访问ui/hooks
:业务逻辑封装,管理组件状态和副作用ui/ducks
:功能模块化,组织相关功能代码
这种架构实现了:
- 关注点分离:UI、状态、逻辑各司其职
- 性能优化:memoization 避免无效重渲染
- 可维护性:模块化设计降低耦合度
- 可测试性:纯函数和独立模块便于测试
更多推荐
所有评论(0)