前端项目设计

408 阅读3分钟

1. 项目组件设计

组件书写思维

  • 需要职责分层

    // Service层:纯业务逻辑
    const userService = {
      async getUsers() { /* API调用 */ },
      async deleteUser(id) { /* 删除逻辑 */ }
    };
    ​
    // Hook层:状态管理和副作用
    const useUserList = () => {
      const [users, setUsers] = useState([]);
      const [loading, setLoading] = useState(false);
    ​
      const deleteUserasync (id) => {
          setLoading(true);
          await userService.deleteUser(id);
          setUsers(prev => prev.filter(u => u.id !== id));
          setLoading(false);
        };
    ​
      return { users, loading, deleteUser };
    };
    ​
    // UI层:纯渲染逻辑
    function UserList() {
      const { users, loading, deleteUser } = useUserList();
    ​
      if (loading) return<LoadingSpinner />;
    ​
      return (
        <div>
          {users.map(user => (
            <UserCard 
              key={user.id} 
              user={user} 
              onDelete={() => deleteUser(user.id)} 
            />
          ))}
        </div>
      );
    }
    

公共组件提取规则

  • 第一次写:直接写
  • 第二次遇到:复制粘贴
  • 第三次遇到:开始抽象

React 组件设计

1. UI + Container 模式

将组件拆分为两类:

  • Container 组件:处理数据获取、state 和逻辑。
  • UI 组件:只负责展示
// Container 组件
function UserContainer() {  
  const { data, isLoading } = useQuery('user', fetchUser);
  return <UserProfile user={data} loading={isLoading} />;
}
​
// UI 组件
function User({ userInfo, loading }) {  
  if (loading) return <Skeleton />;  
  return <div>{userInfo.name}</div>;
}
2. Controlled + Uncontrolled 模式
  • Controlled 组件:父组件通过 props 管理 value。
  • Uncontrolled 组件:内部通过 ref 自行管理 state。
// Controlled 组件
<input value={value} onChange={e => setValue(e.target.value)} />
​
// Uncontrolled 组件
<input defaultValue="Hello" ref={inputRef} />
3. State + Context 模式
  • 通过共享 state 和 context 设计父子组件关系。
  • 父组件管理逻辑,并通过 context 而不是 props 向子组件传递控制权。
<Tabs>  
  <Tabs.List>    
    <Tabs.Trigger value="posts">Posts</Tabs.Trigger>    
    <Tabs.Trigger value="comments">Comments</Tabs.Trigger>  
  </Tabs.List>  
  <Tabs.Panel value="posts">Post content</Tabs.Panel>  
  <Tabs.Panel value="comments">Comment content</Tabs.Panel>
</Tabs>
4. State + Reducer 模式
  • 允许用户覆盖内部的 state 转换逻辑。
  • 组件内部使用 reducer,同时允许消费者注入自定义逻辑。
function useToggle({ reducer = defaultReducer } = {}) {  
  const [state, dispatch] = useReducer(reducer, { onfalse });
  
  const toggle = () => dispatch({ type'toggle' });  
  return [state.on, toggle];
}
5. Props 配置 + Children 组合
  • 使用 props 配置行为。
  • 使用 children 组合 UI。
<Card variant="elevated">  
  <Card.Title>Settings</Card.Title>  
  <Card.Body>    
    <SettingsForm />  
  </Card.Body>
</Card>

2. 项目架构设计

为变化而设计

const createApiClient = (baseURL, defaultHeaders) => ({
  get(endpoint) => fetch(`${baseURL}${endpoint}`, { 
    headers: defaultHeaders 
  }),
  post(endpoint, data) => fetch(`${baseURL}${endpoint}`, {
    method'POST',
    headers: { ...defaultHeaders, 'Content-Type''application/json' },
    bodyJSON.stringify(data)
  })
});
​
const apiClient = createApiClient(
  process.env.API_BASE_URL, 
  { Authorization`Bearer ${getToken()}` }
);

状态管理局部化

  • 局部状态优先,全局状态谨慎
// 组件级状态:用useState/useReducer
function ProductCard() {
  const [isExpanded, setIsExpanded] = useState(false);
  return (<div>
     { isExpanded ? <ProductDetail /> : null }
  </div>)
}
​

import { observable, action, computed, makeObservable } from 'mobx';

// 购物车功能级状态 - 使用 makeObservable
const createShoppingCartStore = () => {
  const store = {
    items: [],
    
    addItem(item) {
      store.items.push(item);
    },
    removeItem(id) {
      store.items = store.items.filter(item => item.id !== id);
    },
    clearCart() {
      store.items = [];
    },
    get totalItems() {
      return store.items.length;
    },
    get totalPrice() {
      return store.items.reduce((total, item) => total + (item.price || 0), 0);
    }
  };

  makeObservable(store, {
    items: observable,
    addItem: action,
    removeItem: action,
    clearCart: action,
    totalItems: computed,
    totalPrice: computed
  });

  return store;
};

// 认证应用级状态 - 使用 makeObservable
const createAuthStore = () => {
  const store = {
    user: null,
    token: null,
    isLoading: false,
    error: null,
    
    async login(credentials) {
      this.isLoading = true;
      this.error = null;
      
      try {
        const response = await fetch('/api/login', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify(credentials),
        });

        if (!response.ok) {
          throw new Error('登录失败');
        }

        const data = await response.json();
        
        runInAction(() => {
          this.user = data.user;
          this.token = data.token;
          this.isLoading = false;
        });
        
      } catch (error) {
        runInAction(() => {
          this.error = error.message;
          this.isLoading = false;
        });
        throw error;
      }
    },
    logout() {
      this.user = null;
      this.token = null;
      this.error = null;
    },
    get isAuthenticated() {
      return !!this.user && !!this.token;
    }
  };

  makeObservable(store, {
    user: observable,
    token: observable,
    isLoading: observable,
    error: observable,
    login: action,
    logout: action,
    isAuthenticated: computed
  });

  return store;
};

路由设计

  • 性能优先的路由设计
const Dashboard = lazy(() =>import('./pages/Dashboard'));
const Analytics = lazy(() =>import('./pages/Analytics'));
const Settings = lazy(() =>import('./pages/Settings'));
​
function App() {
  return (
    <Router>
      <Suspense fallback={<PageSkeleton />}>
        <Routes>
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/analytics" element={<Analytics />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </Router>
  );
}
​
// 基于路由的预加载策略
const useRoutePreloading = () => {
  useEffect(() => {
    // 预加载用户可能访问的路由
    const currentPath = location.pathname;
    if (currentPath === '/dashboard') {
      import('./pages/Analytics'); // 预加载分析页面
    }
  }, []);
};

微前端模块联邦

  • 整合不同技术栈:每个项目可以用不同的技术栈
  • 运行时组合:模块可以动态加载和卸载
  • 共享依赖优化:避免重复加载相同的库
  • 版本独立部署:一个模块的更新不影响其他模块
Webpack项目
// 宿主应用配置
const ModuleFederationPluginrequire('@module-federation/webpack');
​
module.exports = {
  plugins: [
    new ModuleFederationPlugin({
      name'host',
      remotes: {
        userModule'user@http://localhost:3001/remoteEntry.js',
        productModule'product@http://localhost:3002/remoteEntry.js',
        orderModule'order@http://localhost:3003/remoteEntry.js'
      },
      shared: {
        react: { singletontrue },
      }
    })
  ]
};
​
// 动态加载远程模块
const UserManagement = lazy(() =>import('userModule/UserManagement'));
const ProductCatalog = lazy(() =>import('productModule/ProductCatalog'));
​
function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/users/*" element={
          <Suspense fallback={<PageSkeleton />}>
            <UserManagement />
          </Suspense>
        } />
        <Route path="/products/*" element={
          <Suspense fallback={<PageSkeleton />}>
            <ProductCatalog />
          </Suspense>
        } />
      </Routes>
    </BrowserRouter>
  );
}
Umi项目
// .umirc.ts 
import { defineConfig } from 'umi'; 

const shared = { 
    react: { 
        singleton: true, 
        eager: true, 
    }, 
    'react-dom': { 
        singleton: true, 
        eager: true, 
    }, 
}; 

export default defineConfig({ 
    plugins: ['@umijs/plugins/dist/mf'], // 引入插件 
    mf: { 
        remotes: [ 
            { 
                // 可选,未配置则使用当前 remotes[].name 字段 
                aliasName: 'mfNameAlias', 
                name: 'theMfName', 
                entry: 'https://to.the.remote.com/remote.js', 
            }, 
        ], 
        // 配置 MF 共享的模块 
        shared, 
    }, 
});

注:详细教程 umijs.org/docs/max/mf