You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

CASL React上下文报错:Cannot read properties of undefined (reading 'on')

CASL React 上下文实现报错:Cannot read properties of undefined (reading 'on') 解决方法

问题描述

在React上下文提供者中实现CASL React时,持续触发错误:Cannot read properties of undefined (reading 'on')。已定义权限规则,但上下文始终返回undefined,无法定位问题根源,相关代码如下:

原useAbility上下文代码

export const AbilityContext = React.createContext<AnyAbility>({} as AnyAbility);
export const Can = createContextualCan(AbilityContext.Consumer)

export const defineAbilities = (): any => {

  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

  can('read', 'All');
  cannot('delete', 'Extension');
  console.log("Abilities Defined");

  const ability = build();
  return ability
};

interface IAbilityProviderProps {
  children: React.ReactNode;
}
export const AbilityProvider: React.FC<IAbilityProviderProps> = ({ children }) => {

  const { ability } = defineAbilities();
  return (
    <AbilityContext.Provider value={ability} >
      {children}
    </AbilityContext.Provider>
  )
};

export default function useAbilitiesActions() {
  function setAbilities() {
    defineAbilities();
  };

  return { setAbilities }
}

原权限渲染按钮代码

const ability = React.useContext(AbilityContext);
console.log(ability.can('eat', 'Meat'));

return (
  <>
    {
      data
      && (
        <div className={buttonCellClass}>
          <Can
            I="eat"
            an="meat"
            passThrough
            ability={ability}
          >
            <IconButton
              iconProps={deleteIcon}
              ariaLabel="Delete"
              text="Delete"
              onClick={() => {
                props.onDelete(data);
              }}
              disabled={false} //{ability.can('delete', 'Extension')}
            />
          </Can>
        </div>
      )
    }
  </>
);

原App.tsx中Provider包裹代码

<AbilityProvider>
  <EventProvider>
    <Routes>
      <Route path="/" element={<AssetList />} />
      <Route path="/reconciler" element={<Reconciler />} />
      {navigationHelper.map(({ id, url, page: Page }) => (
        <Route
          key={id}
          path={`/${url}`}
          element={(Page && <Page />) || null}
        />
      ))}
    </Routes>
  </EventProvider>
</AbilityProvider>

错误原因分析

  1. 返回值解构错误:defineAbilities函数直接返回Ability实例,但在AbilityProvider中错误地使用const { ability } = defineAbilities()解构,导致ability变量为undefined。
  2. Can组件属性名错误:CASL的Can组件要求使用action和subject属性定义权限,而非自定义的I和an,这会导致组件无法解析权限规则。
  3. 上下文初始值非法:创建AbilityContext时用{} as AnyAbility强制转换类型,但空对象不具备Ability实例的核心方法(如can、on),触发undefined调用错误。

修正后的代码

修正后的useAbility上下文代码

import React from 'react';
import { AbilityBuilder, createMongoAbility, AnyAbility } from '@casl/ability';
import { createContextualCan } from '@casl/react';

// 用空的Ability实例作为初始值,避免undefined问题
export const AbilityContext = React.createContext<AnyAbility>(createMongoAbility());
export const Can = createContextualCan(AbilityContext.Consumer);

export const defineAbilities = (): AnyAbility => {
  const { can, cannot, build } = new AbilityBuilder(createMongoAbility);

  can('read', 'All');
  cannot('delete', 'Extension');
  console.log("Abilities Defined");

  return build();
};

interface IAbilityProviderProps {
  children: React.ReactNode;
}
export const AbilityProvider: React.FC<IAbilityProviderProps> = ({ children }) => {
  // 直接接收返回的Ability实例,无需解构
  const ability = defineAbilities();
  return (
    <AbilityContext.Provider value={ability}>
      {children}
    </AbilityContext.Provider>
  );
};

export default function useAbilitiesActions() {
  function setAbilities() {
    return defineAbilities();
  };

  return { setAbilities };
}

修正后的权限渲染按钮代码

const ability = React.useContext(AbilityContext);
console.log(ability.can('eat', 'Meat'));

return (
  <>
    {data && (
      <div className={buttonCellClass}>
        <Can
          action="eat"
          subject="Meat"
          passThrough
          ability={ability}
        >
          <IconButton
            iconProps={deleteIcon}
            ariaLabel="Delete"
            text="Delete"
            onClick={() => {
              props.onDelete(data);
            }}
            disabled={!ability.can('delete', 'Extension')}
          />
        </Can>
      </div>
    )}
  </>
);

说明

修正后,上下文会正确传递合法的Ability实例,Can组件能正常解析权限规则,undefined相关错误将被解决。同时确保所有使用权限的组件都处于AbilityProvider的包裹范围内。

内容的提问来源于stack exchange,提问作者Conrad Addo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.19 05:10:02