周报@2023-09-06

一些相关问题的总结

styled-components 和 css-modules 对比

https://github.com/ascoders/weekly/blob/master/%E5%89%8D%E6%B2%BF%E6%8A%80%E6%9C%AF/7.%E7%B2%BE%E8%AF%BB%E3%80%8A%E8%AF%B7%E5%81%9C%E6%AD%A2%20css-in-js%20%E7%9A%84%E8%A1%8C%E4%B8%BA%E3%80%8B.md

styled-components 优缺点

  • 优点

    • 使用成本低
      如果是要做一个组件库,让使用方拿着 npm 就能直接用,样式全部自己搞定,不需要依赖其它组件,如 react-dnd 这种,比较适合。
    • 更适合跨平台
      适用于 react-native 这类本身就没有 css 的运行环境。
  • 缺陷

    • 缺乏扩展性
      样式就像小孩的脸,说变就变。比如是最简单的 button,可能在用的时候由于场景不同,就需要设置不同的 font-size,height,width,border 等等,如果全部使用 css-in-js 那将需要把每个样式都变成 props,如果这个组件的 dom 还有多层级呢?你是无法把所有样式都添加到 props 中。同时也不能全部设置成变量,那就丧失了单独定制某个组件的能力。css-in-js 生成的 className 通常是不稳定的随机串,这就给外部想灵活覆盖样式增加了困难。

css-modules 优缺点

  • 优点
    1. CSS Modules 可以有效避免全局污染和样式冲突,能最大化地结合现有 CSS 生态和 JS 模块化能力
    2. 与 SCSS 对比,可以避免 className 的层级嵌套,只使用一个 className 就能把所有样式定义好。
  • 缺点
    1. 与组件库难以配合
    2. 会带来一些使用成本,本地样式覆盖困难,写到最后可能一直在用 :global

HOC 和 hooks

HOC (Higher-Order Component)

HOC 是一个接收一个组件并返回一个新组件的函数。它允许你在不修改其代码的情况下为组件添加额外的功能。HOC 通常用于下列场景:

  1. 代码复用:当你想要在多个组件间共享一些公共逻辑时。
  2. 渲染劫持:基于 props 或 state 改变所渲染的子组件。
  3. 抽象和隔离:为了将不同的关注点(concerns)隔离到不同的HOC中,例如连接到一个 store 或处理某种类型的数据。
  4. 注入 props:为组件添加额外的 props。

Hooks

React Hooks 是从 React 16.8 开始引入的一个新特性,允许你在不使用 class 的情况下使用 state 和其他 React 特性。Hooks 用于以下场景:

  1. 功能逻辑复用:自定义 Hooks 可以帮助你在组件间复用状态逻辑。
  2. 组件内聚:Hooks 允许你在一个组件内组合不同的相关逻辑。
  3. 替代生命周期方法:以前,组件的生命周期事件是通过 class 组件的生命周期方法(如 componentDidMount, componentDidUpdate)来处理的。但现在,可以使用 useEffect Hook 来达到相同的效果。
  4. 更简洁的状态管理:使用 useStateuseReducer,你可以在函数组件中方便地管理状态。
  5. 更好的与第三方库集成:许多第三方库都提供了专门的 Hooks,使得在函数组件中与这些库集成变得更加容易。

何时选择 HOC 还是 Hooks?

  1. 新项目或新组件:对于新的项目或组件,建议优先考虑使用 Hooks,因为它们提供了更加简洁和直观的API。
  2. 类组件的现有项目:如果你在一个使用大量类组件的项目中,使用 HOC 可能更容易集成,尤其是当你不打算立即迁移到函数组件时。
  3. 库和第三方集成:检查你所使用的第三方库或工具。它们可能已经提供了 HOC 或 Hooks,选择与库相匹配的方法会更加方便。
  4. 复用逻辑:如果你正在尝试复用某些逻辑,并且希望这段逻辑与特定的 UI 不紧密绑定,那么自定义 Hooks 是一个非常好的选择。

总之,选择 HOC 还是 Hooks 取决于你的具体需求和现有的代码库。但随着 React 社区逐渐向 Hooks 迁移,建议新的开发或重构时首先考虑使用 Hooks。

React Hooks 可以代替 HOC?

React Hooks 是 React 16.8 之后引入的一个新特性,它允许你在不写 class 组件的情况下使用 state 和其他 React 的功能。在引入 Hooks 之前,React 社区常常使用高阶组件 (Higher-Order Components,简称 HOC) 和 render props 来复用组件逻辑。而 Hooks 提供了一个更加简洁和直观的方式来复用和管理组件逻辑。

所以,可以说 React Hooks 在许多情况下都可以替代 HOC。但这并不意味着 Hooks 完全取代了 HOC。以下是 Hooks 和 HOC 在一些方面的比较:

  1. 复用逻辑:在之前,我们常常使用 HOC 来复用组件之间的逻辑。现在,相同的功能可以通过自定义 Hooks 来实现,代码更加简洁和直观。
  2. 渲染劫持:HOC 可以修改或增加渲染的 JSX,Hooks 则不能直接做到这一点。但通常情况下,你可以通过组合不同的组件和 Hooks 来达到相同的效果。
  3. 静态方法:如果你的组件有静态方法,HOC 可能会使这些方法丢失,除非你手动将它们拷贝到新组件。而 Hooks 在这方面没有这个问题,因为 Hooks 只在函数组件的主体中使用。
  4. Props 注入:HOC 可以向被包装的组件注入 props。这有时可能会导致 props 名称冲突或不可预期的 props 更改。Hooks 不会自动注入 props,但可以返回你想要传递给组件的任何值。
  5. TypeScript 和 Flow:在静态类型检查器,如 TypeScript 或 Flow 中,为 HOC 型态推导可能会有些复杂。使用 Hooks 和函数组件则相对更容易。
  6. 组件层级:每次使用 HOC,你都会在组件树中添加一个新的层级,这可能会使调试工具中的组件层次结构变得复杂。Hooks 不会增加组件层次。

尽管 Hooks 在很多情况下提供了更加简洁和直观的方法来复用和管理组件逻辑,但 HOC 在某些特定情况下仍然有它的价值。例如,与某些第三方库的集成可能仍然需要使用 HOC。

总的来说,虽然 Hooks 在许多常见的用例中可以替代 HOC,但根据你的具体需求和场景,选择最合适的方法仍然很重要。

HOC 例子

高阶组件 (Higher-Order Components, HOC) 是 React 中用于复用组件逻辑的一种模式。一个高阶组件接受一个组件并返回一个新组件。以下是一些经典的 HOC 例子:

  1. withLoading - 根据某个条件显示加载指示器或实际内容。
jsx
1function withLoading(Component) {
2  return function EnhancedComponent({ isLoading, ...props }) {
3    if (isLoading) {
4      return <div>Loading...</div>;
5    }
6    return <Component {...props} />;
7  };
8}

使用:

jsx
1const ListWithLoading = withLoading(List);
2<ListWithLoading isLoading={dataLoading} data={data} />
  1. withErrorBoundary - 提供错误边界功能,捕获子组件树中的 JavaScript 错误。
jsx
1class ErrorBoundary extends React.Component {
2  state = { hasError: false };
3
4  static getDerivedStateFromError(error) {
5    return { hasError: true };
6  }
7
8  componentDidCatch(error, info) {
9    logErrorToService(error, info);
10  }
11
12  render() {
13    if (this.state.hasError) {
14      return <div>Something went wrong!</div>;
15    }
16    return this.props.children;
17  }
18}
19
20function withErrorBoundary(Component) {
21  return props => (
22    <ErrorBoundary>
23      <Component {...props} />
24    </ErrorBoundary>
25  );
26}

使用:

jsx
1const SafeComponent = withErrorBoundary(MyComponent);
  1. withUser - 注入当前用户信息。
jsx
1function withUser(Component) {
2  return function EnhancedComponent(props) {
3    const user = getCurrentUser();
4    return <Component {...props} user={user} />;
5  };
6}

使用:

jsx
1const UserAwareComponent = withUser(SomeComponent);
  1. withRouter - 来自 react-router-dom 的 HOC,为你的组件提供路由属性(如 history, locationmatch)。
jsx
1import { withRouter } from 'react-router-dom';
2
3function ComponentNeedingRouterProps({ history, location, match }) {
4  // 使用 history, location, match 作一些操作
5}
6
7export default withRouter(ComponentNeedingRouterProps);
  1. connect - 来自 react-redux 的 HOC,用于将 Redux store 与 React 组件连接起来。
jsx
1import { connect } from 'react-redux';
2
3function mapStateToProps(state) {
4  return {
5    items: state.items,
6  };
7}
8
9function MyComponent({ items }) {
10  // 使用来自 Redux store 的 items
11}
12
13export default connect(mapStateToProps)(MyComponent);

这些只是一些常见和经典的 HOC 例子。实际上,你可以根据应用的需求创建各种复杂的 HOC,以达到代码复用和逻辑抽象的目的。

React Hooks 例子

自定义 Hooks 是封装和复用组件逻辑的一种方法,特别是当你发现自己在多个组件中重复使用相同的逻辑时。自定义 Hooks 允许你提取这些重复的逻辑到单独的函数中,这样其他组件可以复用它而不需要重复代码。

  1. 使用 API 数据

缘由: 如果你的应用中有多个组件都需要从某个 API 获取数据,你可能会发现自己重复编写相同的状态管理和数据提取逻辑。

自定义 Hook:

jsx
1import { useState, useEffect } from 'react';
2
3function useFetch(url) {
4  const [data, setData] = useState(null);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  useEffect(() => {
9    fetch(url)
10      .then(response => {
11        if (!response.ok) throw new Error("Network response was not ok");
12        return response.json();
13      })
14      .then(data => {
15        setData(data);
16        setLoading(false);
17      })
18      .catch(error => {
19        setError(error);
20        setLoading(false);
21      });
22  }, [url]);
23
24  return { data, loading, error };
25}

使用:

jsx
1function Component() {
2  const { data, loading, error } = useFetch('https://api.example.com/data');
3
4  if (loading) return <div>Loading...</div>;
5  if (error) return <div>Error: {error.message}</div>;
6
7  return <div>{data && data.map(item => <div key={item.id}>{item.name}</div>)}</div>;
8}
  1. 监听窗口大小

缘由: 在响应式设计中,你可能需要根据窗口大小来更改组件的行为或样式。你可能会在多个地方需要这种逻辑。

自定义 Hook:

jsx
1import { useState, useEffect } from 'react';
2
3function useWindowSize() {
4  const [size, setSize] = useState({ width: window.innerWidth, height: window.innerHeight });
5
6  useEffect(() => {
7    const handleResize = () => {
8      setSize({
9        width: window.innerWidth,
10        height: window.innerHeight
11      });
12    };
13
14    window.addEventListener('resize', handleResize);
15    return () => {
16      window.removeEventListener('resize', handleResize);
17    };
18  }, []);
19
20  return size;
21}

使用:

jsx
1function Component() {
2  const { width, height } = useWindowSize();
3
4  return <div>Window size is {width}x{height}</div>;
5}

封装自定义 Hooks 的主要原因是为了复用和抽象组件逻辑,使组件更加清晰、可维护并减少代码冗余。当你在不同的组件或应用中反复使用相同的逻辑时,建议考虑创建一个自定义 Hook。

flex 布局

order 属性是 Flexbox(弹性盒子)布局中的一个重要属性,用于控制 flex 容器中项目的显示顺序。默认情况下,项目在 flex 容器中的排列顺序与其在源代码中的顺序相同。通过给项目分配一个 order 值,你可以控制项目的显示顺序,而不必改变源代码中的顺序。

属性详解:

  • 属性名称order
  • 可能的值:整数(可以为负)
  • 默认值:0

当你设置了项目的 order 属性后,较小的数字会先显示,较大的数字稍后显示。如果多个项目有相同的 order 值,它们之间的顺序将与它们在源代码中的顺序相同。

示例:

假设你有以下 HTML 结构:

html
1<div class="flex-container">
2    <div class="flex-item">1</div>
3    <div class="flex-item">2</div>
4    <div class="flex-item">3</div>
5</div>

以及以下 CSS:

css
1.flex-container {
2    display: flex;
3}
4
5.flex-item:nth-child(1) {
6    order: 2;
7}
8
9.flex-item:nth-child(2) {
10    order: 1;
11}
12
13.flex-item:nth-child(3) {
14    order: 3;
15}

在上述例子中,尽管源代码中的顺序是 1, 2, 3,但由于我们设置的 order 属性,显示顺序会是 2, 1, 3。

在实际应用中,order 属性很有用,因为它允许你在不修改 HTML 源代码的情况下重新排序内容,这在响应式设计中特别有用,因为你可以为不同的屏幕尺寸指定不同的内容顺序。

做题家(建议做题找 ChatGPT,他都会) 并发控制

创建一个简单的 Schedule 类,用于控制并发的 Promise,通常涉及到以下几个步骤:

  1. 用一个队列(数组)来存储待处理的任务。
  2. 用一个队列来存储正在执行的任务。
  3. 当正在执行的任务数量少于指定的并发限制时,从待处理的任务队列中取出任务并开始执行。
  4. 每当有任务完成(无论是成功还是失败),从正在执行的任务队列中移除该任务,并检查是否可以开始下一个任务。
  5. 如果有待处理的任务,继续从步骤3开始执行。

下面是一个基于上述思路的 Schedule 类的实现:

javascript
1class Schedule {
2  constructor(concurrency) {
3    this.concurrency = concurrency; // 最大并发数量
4    this.waiting = []; // 等待执行的任务队列
5    this.running = []; // 正在执行的任务队列
6  }
7
8  // 执行任务
9  execute() {
10    while (this.running.length < this.concurrency && this.waiting.length) {
11      const task = this.waiting.shift(); // 从等待队列中取出任务
12      this.running.push(task);
13
14      task()
15        .then(() => {
16          this._complete(task); // 成功完成后,将任务从正在执行队列中移除
17        })
18        .catch(() => {
19          this._complete(task); // 出错后,将任务从正在执行队列中移除
20        });
21    }
22  }
23
24  // 添加任务
25  add(task) {
26    this.waiting.push(task);
27    this.execute();
28  }
29
30  // 完成任务
31  _complete(task) {
32    const index = this.running.indexOf(task);
33    if (index > -1) {
34      this.running.splice(index, 1); // 从正在执行队列中移除已完成的任务
35    }
36    this.execute(); // 检查是否有新的任务可以开始执行
37  }
38}
39
40// 使用示例
41const scheduler = new Schedule(2); // 最大并发为2
42
43// 模拟异步任务
44function taskGenerator(name, delay) {
45  return () => {
46    console.log(`Task ${name} started`);
47    return new Promise((resolve) => {
48      setTimeout(() => {
49        console.log(`Task ${name} completed`);
50        resolve();
51      }, delay);
52    });
53  };
54}
55
56scheduler.add(taskGenerator('A', 1000));
57scheduler.add(taskGenerator('B', 500));
58scheduler.add(taskGenerator('C', 1200));
59scheduler.add(taskGenerator('D', 700));

上面的代码首先定义了一个 Schedule 类,用于并发控制,然后提供了一个模拟的异步任务生成器 taskGenerator。最后,我们为 scheduler 添加了四个任务,由于我们设置的最大并发为2,所以会看到首先开始执行任务A和B,然后执行任务C和D。