源码: https://github.com/MetaMask/metamask-extension/tree/main/ui

0.整体架构

MetaMask UI端采用了分层架构设计,每一层都有明确的职责:

  • UI层:负责用户界面的展示和交互
  • Hooks层:封装业务逻辑和状态管理
  • Selectors层:提供高效的数据访问和派生
  • Ducks层:模块化的状态管理,每个功能域独立
  • Store层:统一的Redux状态管理中心
  • Background Connection:UI与后台的通信桥梁
  • 后台脚本:核心业务逻辑和数据处理
MetaMask UI 架构
UI层
Hooks层
Selectors层
Ducks层
Store层
Background Connection
后台脚本
Controllers
Services
Utils
submitRequestToBackground
onNotification
setBackgroundConnection
Redux Store
Root Reducer
Middleware
Metamask Duck
App State Duck
Send Duck
Swaps Duck
Gas Duck
History Duck
Account Selectors
Transaction Selectors
Network Selectors
UI State Selectors
useTokenTracker
useGasEstimates
useNotifications
useAccountBalance
账户管理组件
交易确认组件
设置页面组件
代币列表组件

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. 模块间的协作关系

UI Component Custom Hook Selector Duck (Actions/Reducers) Redux Store Background Connection Background Script State Update 1. 用户操作触发Hook 2. 获取当前状态 3. 从Store读取状态 4. 返回状态数据 5. 提供状态数据 6. 派发Action 7. dispatch action到Store 8. 调用Reducer更新状态 9. 调用后台方法 10. 发送请求到后台 11. 处理业务逻辑 12. 返回处理结果 13. 响应结果 14. 返回后台响应 15. 更新Store状态 16. 通知状态变化 17. 提供新状态 18. 触发组件重新渲染 UI Component Custom Hook Selector Duck (Actions/Reducers) Redux Store Background Connection Background Script State Update

5. 总结

MetaMask UI 架构的核心是:

  1. ui/store:状态管理中心,通过 actions 桥接 UI 与 Background
  2. ui/selectors:数据选择与派生,提供 memoized 的数据访问
  3. ui/hooks:业务逻辑封装,管理组件状态和副作用
  4. ui/ducks:功能模块化,组织相关功能代码

这种架构实现了:

  • 关注点分离:UI、状态、逻辑各司其职
  • 性能优化:memoization 避免无效重渲染
  • 可维护性:模块化设计降低耦合度
  • 可测试性:纯函数和独立模块便于测试
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐