前端设计模式实战:用skill frontend-design构建可维护的React应用

3次阅读
没有评论

共计 2301 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

从 Props Drilling 困局说起

最近接手一个遗留项目时,发现这样的组件调用链:

前端设计模式实战:用 skill frontend-design 构建可维护的 React 应用

<Page>
  <Header user={user} />
  <Content 
    user={user}
    onLogout={() => {...}}
  />
  <Footer 
    user={user} 
    links={[...]}
  />
</Page>
  • 用户数据需要穿透 3 层无关组件
  • 任何用户字段变更都会导致全树重渲染
  • 单元测试需要构造完整 props 链

三大解决方案横向对比

方案 1:Context API

const UserContext = createContext<{
  user: User;
  onLogout: () => void;} | null>(null);

// 使用时需要层层校验空值
const {user} = useContext(UserContext)!; // 危险的非空断言 
  • 优点:React 原生支持
  • 缺点:类型安全薄弱,provider 嵌套问题

方案 2:Redux Toolkit

// store/userSlice.ts
const userSlice = createSlice({initialState: { data: null as User | null},
  reducers: {logout: (state) => {...}
  }
});
  • 优点:时间旅行调试
  • 缺点:样板代码多,类型推导复杂

方案 3:设计模式组合(推荐)

复合组件模式实战

// UserProvider.tsx
type UserProviderProps = {
  children: ReactNode;
  initialUser: User;
};

const UserProvider = ({children, initialUser}: UserProviderProps) => {const [user, setUser] = useState(initialUser);

  const contextValue = useMemo(() => ({
    user,
    logout: () => setUser(null!)
  }), [user]);

  return (<UserContext.Provider value={contextValue}>
      {children}
    </UserContext.Provider>
  );
};

// 搭配 Hook 消费
const useUser = () => {const ctx = useContext(UserContext);
  if (!ctx) throw new Error('Missing UserProvider');
  return ctx;
};

自定义 Hook 进阶封装

// useFetchUser.ts
export function useFetchUser<T extends User>(userId: string) {const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState<Error | null>(null);
  const id = useId(); // React 18 新特性

  useEffect(() => {const abortCtrl = new AbortController();

    const fetchData = async () => {
      try {const res = await fetch(`/api/users/${userId}`, {signal: abortCtrl.signal});

        if (!res.ok) throw new Error(`HTTP ${res.status}`);

        const json = await res.json() as T;
        setData(json);
      } catch (err) {if (!abortCtrl.signal.aborted) {setError(err as Error);
        }
      }
    };

    fetchData();
    return () => abortCtrl.abort();
  }, [userId]);

  return {data, error, requestId: id};
}

安全防护要点

XSS 防护

// 错误示范
<div dangerouslySetInnerHTML={{__html: userBio}} />;

// 正确做法
<div>{escapeHtml(userBio)}</div>;

类型守卫

// api/types.ts
function isApiResponse<T>(obj: unknown): obj is ApiResponse<T> {
  return (
    obj != null &&
    typeof obj === 'object' &&
    'data' in obj &&
    'success' in obj
  );
}

// 使用时
if (isApiResponse<User[]>(res)) {// 类型安全访问 res.data}

生产环境检查清单

  1. 单元测试
  2. 核心业务逻辑覆盖率≥80%
  3. 自定义 Hook 必须测试 cleanup 效果

  4. Storybook

    // .storybook/preview.js
    export const parameters = {controls: { expanded: true},
      actions: {argTypesRegex: "^on.*"}
    };

  5. 性能埋点

    // 使用 Web Vitals API
    import {getCLS, getFID, getLCP} from 'web-vitals';
    
    [getCLS, getFID, getLCP].forEach(metric => {metric(console.log);
    });

思考题:复杂度与速度的平衡

在最近的项目中,我们采用了设计模式组合方案后:
– 新成员上手时间增加了 2 天
– 但需求变更响应速度提升了 40%

你的团队会如何选择?欢迎在评论区分享经验

正文完
 0
评论(没有评论)