基于 SOC 职业分类
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/CodySwannGT/lisa --skill container-view-pattern命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
正在显示 SKILL.md
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
any non-trivial request —…
This skill should be used for any non-trivial request — features, bugs, stories, epics, spikes, or multi-step tasks. It accepts a ticket URL (Jira, Linear, GitHub), a file path containing a spec, or a plain-text prompt. It assembles an agent team, breaks the work into structured tasks, and manages the full lifecycle from research through implementation, code review, deploy, and empirical verification.
| name | container-view-pattern |
| description | This skill enforces the… |
This skill provides guidance and validation for the Container/View component pattern used in this codebase.
The Container/View pattern separates components into two distinct files:
*Container.tsx): Handles logic, state, API calls, data fetching, and event handlers*View.tsx): Handles rendering UI only, receiving all data and callbacks as propsindex.tsx): Exports the Container as the default componentThe Container/View pattern is required in these directories:
| Directory | Applies | Notes |
|---|---|---|
features/*/components/ | Yes | All feature components |
features/*/screens/ | Yes | All feature screens |
components/ | Yes | Shared components |
screens/ | Yes | Shared screens |
components/ui/ | No | UI primitives (GlueStack) |
components/shared/ | No | Simple shared utilities |
components/icons/ | No | Icon components |
Run the skill's generator script for any component type:
python3 .claude/skills/container-view-pattern/scripts/create_component.py <type> <name> [feature]
Component Types:
| Type | Command | Creates in |
|---|---|---|
| Global component | create_component.py global-component PlayerCard | components/PlayerCard/ |
| Feature component | create_component.py feature-component PlayerCard player-kanban | features/player-kanban/components/PlayerCard/ |
| Global screen | create_component.py global-screen Settings | screens/Settings/ |
| Feature screen | create_component.py feature-screen Main dashboard | features/dashboard/screens/Main/ |
Create the following directory structure:
ComponentName/
├── ComponentNameContainer.tsx
├── ComponentNameView.tsx
└── index.tsx
Container components handle all business logic:
useState, useReduceruseMemouseCallback with proper dependenciesContainers must follow this specific order:
const ExampleContainer = () => {
// 1. Variables, state, useMemo, useCallback (same group)
const [state, setState] = useState();
const computed = useMemo(() => state * 2, [state]);
const handleClick = useCallback(() => {}, []);
// 2. useEffect hooks
useEffect(() => {
// side effects
}, []);
// 3. Return statement (always last)
return <ExampleView />;
};
import { useCallback, useMemo, useState } from "react";
import ComponentNameView from "./ComponentNameView";
/**
* Props for the ComponentName component.
*/
interface ComponentNameProps {
readonly id: string;
}
/**
* Container component that manages state and logic for ComponentName.
* @param props - Component properties
* @param props.id - The unique identifier
*/
const ComponentNameContainer = ({ id }: ComponentNameProps) => {
// State
const [isLoading, setIsLoading] = useState(false);
// Memoized computed values
const formattedData = useMemo(() => {
return data?.toUpperCase() ?? "";
}, [data]);
// Event handlers wrapped in useCallback
const handleSubmit = useCallback(() => {
setIsLoading(true);
}, []);
return (
<ComponentNameView
formattedData={formattedData}
isLoading={isLoading}
onSubmit={handleSubmit}
/>
);
};
;
View components are pure presentation:
() => (...) not () => { return (...); }memo() for performance optimizationComponentName.displayName = "ComponentName"readonlyuseState, useEffect, useMemo, etc.import { memo } from "react";
import { Box } from "@/components/ui/box";
import { Text } from "@/components/ui/text";
/**
* Props for the ComponentNameView component.
*/
interface ComponentNameViewProps {
readonly formattedData: string;
readonly isLoading: boolean;
readonly onSubmit: () => void;
}
/**
* View component that renders the ComponentName UI.
* @param props - Component properties
* @param props.formattedData - Pre-formatted display data
* @param props.isLoading - Loading state indicator
* @param props.onSubmit - Submit handler callback
*/
const ComponentNameView = ({
formattedData,
isLoading,
onSubmit,
}: ComponentNameViewProps) => (
<Box testID="COMPONENT_NAME.CONTAINER">
{isLoading ? <Text>Loading...</Text> : <Text>{formattedData}</Text>}
</Box>
);
ComponentNameView.displayName = ;
();
Export the Container as the default:
export { default } from "./ComponentNameContainer";
The following ESLint rules enforce the pattern:
| Rule | Description |
|---|---|
component-structure/enforce-component-structure | Validates directory structure and file naming |
component-structure/no-return-in-view | Ensures View uses arrow shorthand |
component-structure/require-memo-in-view | Ensures View uses memo and displayName |
component-structure/single-component-per-file | One component per file |
Run the validation script to check a component:
python3 .claude/skills/container-view-pattern/scripts/validate_component.py <path-to-component-directory>
Run ESLint to check all components:
bun run lint
Note: Replace
bunwith your project's package manager (npm,yarn,pnpm) as needed.
| Issue | Resolution |
|---|---|
| Rendering UI elements besides View | Container must ONLY return the corresponding View component |
| Rendering multiple components | Move all UI to View; Container returns only View |
Missing useMemo for objects/arrays | Wrap computed values in useMemo |
Missing useCallback for functions | Wrap handlers in useCallback |
| Logic in View component | Move logic to Container |
| Inline function props | Create memoized handler |
| Issue | Resolution |
|---|---|
Using block body { return } | Convert to arrow shorthand () => (...) |
Missing memo wrapper | Add export default memo(ComponentView) |
Missing displayName | Add ComponentView.displayName = "ComponentView" |
| Contains hooks | Move hooks to Container |
| Contains state | Move state to Container |
When View components exceed ESLint's cognitive complexity threshold (28), extract render helper functions. For simple cases, prefer inline JSX:
/**
* Renders the loading skeleton state.
* @param props - Helper function properties
* @param props.isDark - Whether dark mode is active
*/
function renderLoadingState(props: { readonly isDark: boolean }) {
const { isDark } = props;
return <LoadingSkeleton isDark={isDark} />;
}
const ComponentView = ({ isLoading, isDark }: Props) => (
<Box>{isLoading ? renderLoadingState({ isDark }) : <Content />}</Box>
);
handle* prefix (e.g., handleSubmit, handleClick)on* prefix (e.g., onSubmit, onClick)// Container
const handleSubmit = useCallback(() => { ... }, []);
return <ComponentView onSubmit={handleSubmit} />;
// View
const ComponentView = ({ onSubmit }: Props) => (
<Button onPress={onSubmit}>Submit</Button>
);
For detailed examples and edge cases, read:
references/patterns.md - Common patterns and anti-patternsreferences/examples.md - Complete component examples