普通视图

发现新文章,点击刷新页面。
昨天 — 2026年3月9日首页

ahooks useRequest 深度解析:一个 Hook 搞定所有请求

作者 兆子龙
2026年3月9日 18:26

二、核心功能详解

1. 自动管理请求状态

const { data, loading, error, run, refresh, cancel } = useRequest(
  fetchUserList,
  {
    manual: false,  // 自动执行
    defaultParams: [{ page: 1 }],  // 默认参数
  }
);

// run: 手动触发
// refresh: 使用上次参数重新请求
// cancel: 取消当前请求

2. 防抖与节流

// 搜索场景:防抖
const { data, loading } = useRequest(searchAPI, {
  debounceWait: 300,  // 300ms 防抖
  manual: true,
});

// 滚动加载:节流
const { run } = useRequest(loadMore, {
  throttleWait: 1000,  // 1s 节流
  manual: true,
});

3. 轮询

// 每 3 秒轮询一次
const { data } = useRequest(getStatus, {
  pollingInterval: 3000,
  pollingWhenHidden: false,  // 页面隐藏时停止轮询
});

// 条件轮询
const { data } = useRequest(getJobStatus, {
  pollingInterval: 2000,
  pollingErrorRetryCount: 3,  // 错误重试次数
  onSuccess: (result) => {
    if (result.status === 'completed') {
      // 完成后停止轮询
      return false;
    }
  }
});

4. 依赖刷新

const [userId, setUserId] = useState('1');

const { data } = useRequest(
  () => fetchUser(userId),
  {
    refreshDeps: [userId],  // userId 变化时自动重新请求
  }
);

5. 缓存机制

// SWR 模式:先返回缓存,后台更新
const { data, loading } = useRequest(fetchUser, {
  cacheKey: 'user-data',
  staleTime: 5000,  // 5s 内认为数据新鲜
  cacheTime: 300000,  // 缓存保留 5 分钟
});

// 清除缓存
import { clearCache } from 'ahooks';
clearCache('user-data');

6. 错误重试

const { data, error, retry } = useRequest(unstableAPI, {
  retryCount: 3,  // 失败后重试 3 次
  retryInterval: 1000,  // 重试间隔 1s
  onError: (error, params) => {
    console.log('请求失败', error);
  }
});

三、进阶场景

并行请求

const user = useRequest(fetchUser);
const posts = useRequest(fetchPosts);
const comments = useRequest(fetchComments);

const loading = user.loading || posts.loading || comments.loading;

串行请求

const { data: user } = useRequest(fetchUser);

const { data: posts } = useRequest(
  () => fetchUserPosts(user.id),
  {
    ready: !!user,  // user 存在时才执行
    refreshDeps: [user],
  }
);

分页加载

function UserList() {
  const { data, loading, loadMore, loadingMore, noMore } = useRequest(
    (d) => fetchList({ page: d?.nextPage || 1 }),
    {
      loadMore: true,
      isNoMore: (d) => !d?.hasMore,
    }
  );
  
  return (
    <>
      {data?.list.map(item => <Item key={item.id} {...item} />)}
      {!noMore && (
        <Button onClick={loadMore} loading={loadingMore}>
          加载更多
        </Button>
      )}
    </>
  );
}

乐观更新

const { run: deleteItem } = useRequest(deleteAPI, {
  manual: true,
  onBefore: (params) => {
    // 立即更新 UI
    setList(list => list.filter(item => item.id !== params[0]));
  },
  onError: (error, params) => {
    // 失败时回滚
    message.error('删除失败');
    refresh();
  }
});

四、与其他方案对比

特性 useRequest React Query SWR
学习成本
功能完整度 很高
包体积 较大
防抖节流 内置 需自己实现 需自己实现
轮询 内置 内置 需配置
TypeScript 良好 优秀 良好

五、最佳实践

  1. 合理使用缓存:列表、详情等读多写少的数据适合缓存
  2. 设置合适的防抖时间:搜索建议 300-500ms
  3. 避免过度轮询:根据业务需求设置合理的轮询间隔
  4. 善用 ready 参数:避免无效请求
  5. 统一错误处理:在全局配置中处理通用错误
// 全局配置
import { configResponsive } from 'ahooks';

configResponsive({
  onError: (error) => {
    if (error.code === 401) {
      // 统一处理未登录
      redirectToLogin();
    }
  }
});

六、源码解析(简化版)

useRequest 的核心实现思路:

function useRequest(service, options) {
  const [state, setState] = useState({
    data: undefined,
    loading: false,
    error: undefined,
  });
  
  const run = useCallback(async (...params) => {
    setState(s => ({ ...s, loading: true }));
    
    try {
      const data = await service(...params);
      setState({ data, loading: false, error: undefined });
    } catch (error) {
      setState(s => ({ ...s, loading: false, error }));
    }
  }, [service]);
  
  useEffect(() => {
    if (!options.manual) {
      run(...(options.defaultParams || []));
    }
  }, []);
  
  return { ...state, run };
}

实际实现还包括:

  • 防抖节流的 debounce/throttle 包装
  • 轮询的 setInterval 管理
  • 缓存的 Map 存储
  • 依赖追踪的 useEffect
  • 请求取消的 AbortController

总结

useRequest 是一个功能强大且易用的请求管理 Hook,它封装了日常开发中 90% 的请求场景。通过合理使用其提供的能力,可以大幅减少样板代码,提升开发效率。

推荐在中小型项目中直接使用 useRequest,大型项目可以考虑 React Query 获得更强的数据管理能力。

如果这篇文章对你有帮助,欢迎点赞收藏!

React Suspense 从入门到实战:让异步加载更优雅

作者 兆子龙
2026年3月9日 17:34

二、代码分割场景

这是 Suspense 最成熟的应用场景,配合 React.lazy 实现组件懒加载。

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./Dashboard'));
const Settings = lazy(() => import('./Settings'));

function App() {
  return (
    <Suspense fallback={<PageLoader />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

多层 Suspense 边界:

<Suspense fallback={<AppShell />}>
  <Layout>
    <Suspense fallback={<SidebarSkeleton />}>
      <Sidebar />
    </Suspense>
    <Suspense fallback={<ContentSkeleton />}>
      <Content />
    </Suspense>
  </Layout>
</Suspense>

三、数据请求场景

React 18 开始,Suspense 可以配合支持的数据请求库使用。

使用 SWR:

import useSWR from 'swr';

function User({ id }) {
  const { data } = useSWR(`/api/user/${id}`, fetcher, {
    suspense: true  // 开启 Suspense 模式
  });
  
  return <div>{data.name}</div>;
}

<Suspense fallback={<UserSkeleton />}>
  <User id={123} />
</Suspense>

使用 React Query:

import { useQuery } from '@tanstack/react-query';

function Posts() {
  const { data } = useQuery({
    queryKey: ['posts'],
    queryFn: fetchPosts,
    suspense: true
  });
  
  return data.map(post => <Post key={post.id} {...post} />);
}

四、与 Error Boundary 配合

Suspense 只处理"挂起"状态,错误需要 Error Boundary 捕获。

class ErrorBoundary extends React.Component {
  state = { hasError: false };
  
  static getDerivedStateFromError(error) {
    return { hasError: true };
  }
  
  render() {
    if (this.state.hasError) {
      return <ErrorFallback />;
    }
    return this.props.children;
  }
}

<ErrorBoundary>
  <Suspense fallback={<Loading />}>
    <DataComponent />
  </Suspense>
</ErrorBoundary>

五、与并发特性配合

配合 useTransition 避免不必要的 loading 状态:

function SearchResults() {
  const [query, setQuery] = useState('');
  const [isPending, startTransition] = useTransition();
  
  const handleSearch = (value) => {
    startTransition(() => {
      setQuery(value);  // 低优先级更新
    });
  };
  
  return (
    <>
      <input onChange={e => handleSearch(e.target.value)} />
      {isPending && <InlineSpinner />}
      <Suspense fallback={<ResultsSkeleton />}>
        <Results query={query} />
      </Suspense>
    </>
  );
}

六、最佳实践

  1. 合理划分边界:按路由或功能模块设置 Suspense,避免过细或过粗
  2. 提供有意义的 fallback:使用骨架屏而非简单的 loading 文字
  3. 避免瀑布流:并行发起请求,而非串行等待
  4. 配合预加载:在用户交互前提前触发数据请求
// 预加载示例
const resource = fetchUser(id);  // 提前发起

function Profile() {
  const user = use(resource);  // 直接使用
  return <div>{user.name}</div>;
}

七、注意事项

  • Suspense 在服务端渲染(SSR)中需要特殊处理,React 18 提供了流式 SSR 支持
  • 不是所有数据请求库都支持 Suspense,使用前查看文档
  • fallback 组件应该是轻量的,避免在其中执行副作用
  • 嵌套 Suspense 时,内层边界会优先生效

总结

Suspense 让异步操作的处理更加优雅和声明式。从代码分割到数据请求,它都能提供更好的开发体验和用户体验。配合 React 18 的并发特性,Suspense 将成为构建现代 React 应用的重要工具。

如果这篇文章对你有帮助,欢迎点赞收藏!

ahooks useRequest 深度解析:一个 Hook 搞定所有请求

作者 兆子龙
2026年3月9日 17:33

二、核心功能详解

1. 自动管理请求状态

const { data, loading, error, run, refresh, cancel } = useRequest(
  fetchUserList,
  {
    manual: false,  // 自动执行
    defaultParams: [{ page: 1 }],  // 默认参数
  }
);

// run: 手动触发
// refresh: 使用上次参数重新请求
// cancel: 取消当前请求

2. 防抖与节流

// 搜索场景:防抖
const { data, loading } = useRequest(searchAPI, {
  debounceWait: 300,  // 300ms 防抖
  manual: true,
});

// 滚动加载:节流
const { run } = useRequest(loadMore, {
  throttleWait: 1000,  // 1s 节流
  manual: true,
});

3. 轮询

// 每 3 秒轮询一次
const { data } = useRequest(getStatus, {
  pollingInterval: 3000,
  pollingWhenHidden: false,  // 页面隐藏时停止轮询
});

// 条件轮询
const { data } = useRequest(getJobStatus, {
  pollingInterval: 2000,
  pollingErrorRetryCount: 3,  // 错误重试次数
  onSuccess: (result) => {
    if (result.status === 'completed') {
      // 完成后停止轮询
      return false;
    }
  }
});

4. 依赖刷新

const [userId, setUserId] = useState('1');

const { data } = useRequest(
  () => fetchUser(userId),
  {
    refreshDeps: [userId],  // userId 变化时自动重新请求
  }
);

5. 缓存机制

// SWR 模式:先返回缓存,后台更新
const { data, loading } = useRequest(fetchUser, {
  cacheKey: 'user-data',
  staleTime: 5000,  // 5s 内认为数据新鲜
  cacheTime: 300000,  // 缓存保留 5 分钟
});

// 清除缓存
import { clearCache } from 'ahooks';
clearCache('user-data');

6. 错误重试

const { data, error, retry } = useRequest(unstableAPI, {
  retryCount: 3,  // 失败后重试 3 次
  retryInterval: 1000,  // 重试间隔 1s
  onError: (error, params) => {
    console.log('请求失败', error);
  }
});

三、进阶场景

并行请求

const user = useRequest(fetchUser);
const posts = useRequest(fetchPosts);
const comments = useRequest(fetchComments);

const loading = user.loading || posts.loading || comments.loading;

串行请求

const { data: user } = useRequest(fetchUser);

const { data: posts } = useRequest(
  () => fetchUserPosts(user.id),
  {
    ready: !!user,  // user 存在时才执行
    refreshDeps: [user],
  }
);

分页加载

function UserList() {
  const { data, loading, loadMore, loadingMore, noMore } = useRequest(
    (d) => fetchList({ page: d?.nextPage || 1 }),
    {
      loadMore: true,
      isNoMore: (d) => !d?.hasMore,
    }
  );
  
  return (
    <>
      {data?.list.map(item => <Item key={item.id} {...item} />)}
      {!noMore && (
        <Button onClick={loadMore} loading={loadingMore}>
          加载更多
        </Button>
      )}
    </>
  );
}

乐观更新

const { run: deleteItem } = useRequest(deleteAPI, {
  manual: true,
  onBefore: (params) => {
    // 立即更新 UI
    setList(list => list.filter(item => item.id !== params[0]));
  },
  onError: (error, params) => {
    // 失败时回滚
    message.error('删除失败');
    refresh();
  }
});

四、与其他方案对比

特性 useRequest React Query SWR
学习成本
功能完整度 很高
包体积 较大
防抖节流 内置 需自己实现 需自己实现
轮询 内置 内置 需配置
TypeScript 良好 优秀 良好

五、最佳实践

  1. 合理使用缓存:列表、详情等读多写少的数据适合缓存
  2. 设置合适的防抖时间:搜索建议 300-500ms
  3. 避免过度轮询:根据业务需求设置合理的轮询间隔
  4. 善用 ready 参数:避免无效请求
  5. 统一错误处理:在全局配置中处理通用错误
// 全局配置
import { configResponsive } from 'ahooks';

configResponsive({
  onError: (error) => {
    if (error.code === 401) {
      // 统一处理未登录
      redirectToLogin();
    }
  }
});

六、源码解析(简化版)

useRequest 的核心实现思路:

function useRequest(service, options) {
  const [state, setState] = useState({
    data: undefined,
    loading: false,
    error: undefined,
  });
  
  const run = useCallback(async (...params) => {
    setState(s => ({ ...s, loading: true }));
    
    try {
      const data = await service(...params);
      setState({ data, loading: false, error: undefined });
    } catch (error) {
      setState(s => ({ ...s, loading: false, error }));
    }
  }, [service]);
  
  useEffect(() => {
    if (!options.manual) {
      run(...(options.defaultParams || []));
    }
  }, []);
  
  return { ...state, run };
}

实际实现还包括:

  • 防抖节流的 debounce/throttle 包装
  • 轮询的 setInterval 管理
  • 缓存的 Map 存储
  • 依赖追踪的 useEffect
  • 请求取消的 AbortController

总结

useRequest 是一个功能强大且易用的请求管理 Hook,它封装了日常开发中 90% 的请求场景。通过合理使用其提供的能力,可以大幅减少样板代码,提升开发效率。

推荐在中小型项目中直接使用 useRequest,大型项目可以考虑 React Query 获得更强的数据管理能力。

如果这篇文章对你有帮助,欢迎点赞收藏!

【React】19 深度解析:掌握新一代 React 特性

作者 兆子龙
2026年3月9日 11:43

【React】19 深度解析:掌握新一代 React 特性

从 Actions、use()、ref 作 prop 到表单与 Server Components,一文梳理 React 19 稳定版核心特性与落地方式。


一、React 19 是什么

React 19 是 React 团队在 2024 年 12 月 5 日 发布的稳定版本,在 npm 上可直接安装使用。它在保留 React 18 并发与 Suspense 能力的基础上,重点增强了异步数据与表单(Actions)、在渲染中消费资源use())、ref 与 Context 用法,并正式纳入 Server Components / Server Actions 的稳定能力,方便与支持全栈架构的框架(如 Next.js)配合使用。

与 18 相比,19 更强调「用声明式方式处理异步与表单」,减少手写 pending/error、减少 forwardRef 与 Context 的样板代码,同时改进水合报错、文档元数据等开发体验。


二、核心新特性概览

方向 内容
Actions 异步函数放进 useTransition,自动管理 pending、错误、表单重置;配合 useActionStateuseOptimistic<form action={fn}>
use() 在渲染中读取 Promise 或 Context,可条件调用,需配合 Suspense
ref 函数组件可直接用 ref 作 prop,不再强制 forwardRef
表单 <form> 支持 action/formAction 为函数;useFormStatus 读父级表单状态;requestFormReset 手动重置
Context 可用 <Context value={...}> 作 Provider,替代 <Context.Provider>
Server Server Components、Server Actions 在 React 19 中稳定,需框架/打包器支持

下面按块说明用法与注意点。


三、Actions:异步与表单一体化

Actions 指在 transition 里跑的异步函数:React 自动提供 pending 状态、错误边界内的错误处理、表单提交后重置(非受控)、以及乐观更新的回滚。

用 useTransition 跑异步

以前要自己用 useStateisPendingerror;现在把异步逻辑放进 startTransition 即可,pending 由 React 管理:

function UpdateName() {
  const [name, setName] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  const handleSubmit = () => {
    startTransition(async () => {
      const err = await updateName(name);
      if (err) {
        setError(err);
        return;
      }
      redirect("/path");
    });
  };

  return (
    <>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <button onClick={handleSubmit} disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </>
  );
}

useActionState:封装 Action + 状态

把「服务端/异步操作 + 上一次结果 + pending」打包成一个 Hook,适合表单提交:

function ChangeName({ name, setName }) {
  const [error, submitAction, isPending] = useActionState(
    async (previousState, formData) => {
      const err = await updateName(formData.get("name"));
      if (err) return err;
      redirect("/path");
      return null;
    },
    null
  );

  return (
    <form action={submitAction}>
      <input type="text" name="name" defaultValue={name} />
      <button type="submit" disabled={isPending}>Update</button>
      {error && <p>{error}</p>}
    </form>
  );
}

要点useActionState 第一个参数是 Action(可接收 previousState 与表单等入参),返回的 submitAction 可直接作为 <form action={submitAction}>,提交成功后 React 会重置非受控表单。

useOptimistic:乐观更新

在请求尚未返回前先更新 UI,失败时 React 会回滚到真实状态。需在 Action 内调用 setOptimisticXxx

function ChangeName({ currentName, onUpdateName }) {
  const [optimisticName, setOptimisticName] = useOptimistic(currentName);

  const submitAction = async (formData) => {
    const newName = formData.get("name");
    setOptimisticName(newName);
    const updated = await updateName(newName);
    onUpdateName(updated);
  };

  return (
    <form action={submitAction}>
      <p>Your name is: {optimisticName}</p>
      <input type="text" name="name" disabled={currentName !== optimisticName} />
    </form>
  );
}

useFormStatus(react-dom)

父级 <form> 内的子组件里,用 useFormStatus() 拿到该表单的 pending 等状态,无需层层传 props:

import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return <button type="submit" disabled={pending}>Submit</button>;
}

function MyForm() {
  return (
    <form action={someAction}>
      <input name="field" />
      <SubmitButton />
    </form>
  );
}

四、use():在渲染中读 Promise / Context

use(resource) 可在组件渲染时消费两种资源:

  1. Promise:未 resolve 时组件挂起,由上层 <Suspense> 显示 fallback;resolve 后得到数据再渲染。
  2. Context:等价于「可条件调用的 useContext」,可在 early return 之后调用,这是和 Hooks 规则的重要区别。

读 Promise(配 Suspense)

import { use } from "react";

function Comments({ commentsPromise }) {
  const comments = use(commentsPromise);
  return comments.map((c) => <p key={c.id}>{c.text}</p>);
}

function Page({ commentsPromise }) {
  return (
    <Suspense fallback={<div>Loading...</div>}>
      <Comments commentsPromise={commentsPromise} />
    </Suspense>
  );
}

注意:不要在渲染里现场 new Promise 再传给 use(),React 会警告「uncached promise」。应使用支持缓存的数据源(如框架或 Suspense 兼容库提供的 promise)。

读 Context(可条件调用)

import { use } from "react";

function Heading({ children }) {
  if (children == null) return null;
  const theme = use(ThemeContext);  // 在 return 之后调用,useContext 做不到
  return <h1 style={{ color: theme.color }}>{children}</h1>;
}

五、ref 作为 prop、Context 作为 Provider

ref 直接当 prop

函数组件不再必须用 forwardRef,可直接声明 ref 并转发给 DOM 或子组件:

function MyInput({ placeholder, ref }) {
  return <input placeholder={placeholder} ref={ref} />;
}
// 使用:<MyInput ref={ref} />

未来版本中 forwardRef 将被弃用,官方会提供 codemod 协助迁移。

Context 直接当 Provider

可以用 <Context value={...}> 替代 <Context.Provider value={...}>

const ThemeContext = createContext("");

function App({ children }) {
  return (
    <ThemeContext value="dark">
      {children}
    </ThemeContext>
  );
}

六、文档元数据、ref 清理与其它

  • 文档元数据:在组件里直接渲染 <title><meta> 等,React 会提升到 <head>(需在支持该能力的框架/环境中使用)。
  • ref 回调清理:ref 回调可返回一个清理函数,在节点从 DOM 移除时执行。
  • 水合错误react-dom 对水合不匹配的报错做了改进,会给出更清晰的 diff 与说明链接。
  • React DOM 静态 APIreact-dom/static 提供 prerenderprerenderToNodeStream 等,用于在等待数据加载后输出静态 HTML 流。
  • Server Components / Server Actions:在 React 19 中稳定,需由支持全栈架构的框架(如 Next.js)或自定义打包器实现;"use server" 仅用于标记 Server Action,不用于标记 Server Component。

七、如何升级与参考


总结

  • Actions:用 useTransition 跑异步、useActionState 包表单、useOptimistic 做乐观更新,<form action={fn}>useFormStatus 简化表单状态。
  • use():在渲染中读 Promise(配 Suspense)或 Context,且 use() 可条件调用。
  • ref / Context:函数组件可直接用 ref 作 prop;可用 <Context value={...}> 作 Provider。
  • Server:Server Components 与 Server Actions 在 React 19 稳定,需框架支持;文档元数据、静态预渲染等需在对应环境中使用。

若对你有用,欢迎点赞、收藏;你若有 React 19 落地或迁移经验,也欢迎在评论区分享。

昨天以前首页

CSS 里的「if」:@media、@supports 与即将到来的 @when/@else

作者 兆子龙
2026年3月8日 14:18

CSS 里的「if」:@media、@supports 与即将到来的 @when/@else

梳理 CSS 中实现「条件判断」的几种方式:媒体查询、特性查询,以及规范中的 @when/@else,并给出简单用法与兼容性说明。


一、CSS 有 if 吗?

CSS 没有像 JavaScript 那样的 if (x) { } 语句,但可以通过 @ 规则 做「条件式」样式:满足某条件时才应用某段样式。常见的有两类:媒体查询(@media)特性查询(@supports);规范里还有正在推进的 @when / @else,写法更接近「if-else」,但目前浏览器尚未普遍支持。下面按「能用 today」和「即将到来」分开说。


二、@media:按视口/设备「if」

@media 用来根据媒体类型与媒体特征(如视口宽度、横竖屏、分辨率)决定是否应用样式,相当于「如果屏幕满足某条件,用这段 CSS」。

/* 视口宽度 ≥ 768px 时用栅格布局 */
@media (min-width: 768px) {
  .grid {
    display: grid;
    grid-template-columns: repeat(3, 1fr);
  }
}

/* 横屏时调整内边距 */
@media (orientation: landscape) {
  .panel {
    padding: 2rem;
  }
}

常见条件min-width / max-widthmin-heightorientationprefers-color-scheme(深色/浅色)、prefers-reduced-motion 等。多条件用 and 连接;需要「或」时写多个 @media 或在一个规则里用逗号。兼容性:现代浏览器均支持,是响应式布局的基础。


三、@supports:按浏览器能力「if」

@supports特性查询如果浏览器支持某 CSS 属性或语法,应用这段样式;不支持则跳过。适合做渐进增强(先写基础样式,再在支持新特性的浏览器里增强)。

/* 支持 Grid 时用 Grid 布局 */
@supports (display: grid) {
  .container {
    display: grid;
    gap: 1rem;
  }
}

/* 不支持时回退 */
@supports not (display: grid) {
  .container {
    display: flex;
    flex-wrap: wrap;
  }
}

/* 同时支持多个特性时 */
@supports (display: grid) and (gap: 1rem) {
  .container {
    display: grid;
    gap: 1rem;
  }
}

逻辑@supports (条件)notandor;还可检测选择器,如 @supports selector(:has(a))兼容性:主流浏览器早已支持,可放心用。


四、@when / @else:规范里的「if-else」(即将到来)

@when@elseCSS Conditional Rules Level 5 中的新规则,用来统一写条件:把媒体条件、特性支持等写进同一套「when-else」链里,语义更接近「if-else if-else」,减少多层 @media 嵌套。

示例(语法以最终规范为准)

@when media(min-width: 800px) {
  .sidebar { width: 300px; }
}
@else media(min-width: 600px) {
  .sidebar { width: 240px; }
}
@else {
  .sidebar { width: 100%; }
}

还可组合 mediasupports

@when media(min-width: 1024px) and supports(display: grid) {
  .layout { display: grid; }
}
@else {
  .layout { display: block; }
}

现状:截至 2024–2025 年,主流浏览器尚未支持 @when/@else,目前只能在支持该规范的实验环境或未来版本中使用。写新项目时仍以 @media + @supports 为主;等 @when/@else 普及后,再考虑重构为更简洁的条件链。


五、对比与使用建议

方式 作用 兼容性 典型场景
@media 视口/设备条件 全面支持 响应式、深色模式、动效偏好
@supports 浏览器能力条件 全面支持 渐进增强、Grid/Flex 回退
@when/@else 统一条件链 尚未支持 未来多条件、互斥分支

建议

  • 需要「根据屏幕大小/横竖屏/主题」切换样式 → 用 @media
  • 需要「根据是否支持某 CSS 特性」切换样式 → 用 @supports
  • 两者可以组合:先 @media 再在块内写 @supports,或反过来。
  • @when/@else 先了解语法即可,等 Can I Use 显示普遍支持后再在实际项目中使用。

六、小结

  • CSS 没有字面意义的 if,但用 @media(媒体条件)和 @supports(特性条件)可以实现「满足条件才应用样式」。
  • @media:按视口宽度、横竖屏、prefers-* 等写响应式与偏好适配。
  • @supports:按浏览器是否支持某属性/选择器写渐进增强与回退。
  • @when/@else:规范中的统一条件语法,可读性更好,目前浏览器未支持,可关注 CSS Conditional Level 5 与 Can I Use 的更新。

若对你有用,欢迎点赞、收藏;你若有基于 @supports 或 @media 的实战写法,也欢迎在评论区分享。

❌
❌