周报@2023-09-06
一些相关问题的总结
styled-components 和 css-modules 对比
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 优缺点
- 优点
- CSS Modules 可以有效避免全局污染和样式冲突,能最大化地结合现有 CSS 生态和 JS 模块化能力
- 与 SCSS 对比,可以避免 className 的层级嵌套,只使用一个 className 就能把所有样式定义好。
- 缺点
- 与组件库难以配合
- 会带来一些使用成本,本地样式覆盖困难,写到最后可能一直在用 :global。
HOC 和 hooks
HOC (Higher-Order Component)
HOC 是一个接收一个组件并返回一个新组件的函数。它允许你在不修改其代码的情况下为组件添加额外的功能。HOC 通常用于下列场景:
- 代码复用:当你想要在多个组件间共享一些公共逻辑时。
- 渲染劫持:基于 props 或 state 改变所渲染的子组件。
- 抽象和隔离:为了将不同的关注点(concerns)隔离到不同的HOC中,例如连接到一个 store 或处理某种类型的数据。
- 注入 props:为组件添加额外的 props。
Hooks
React Hooks 是从 React 16.8 开始引入的一个新特性,允许你在不使用 class 的情况下使用 state 和其他 React 特性。Hooks 用于以下场景:
- 功能逻辑复用:自定义 Hooks 可以帮助你在组件间复用状态逻辑。
- 组件内聚:Hooks 允许你在一个组件内组合不同的相关逻辑。
- 替代生命周期方法:以前,组件的生命周期事件是通过 class 组件的生命周期方法(如
componentDidMount
,componentDidUpdate
)来处理的。但现在,可以使用useEffect
Hook 来达到相同的效果。 - 更简洁的状态管理:使用
useState
和useReducer
,你可以在函数组件中方便地管理状态。 - 更好的与第三方库集成:许多第三方库都提供了专门的 Hooks,使得在函数组件中与这些库集成变得更加容易。
何时选择 HOC 还是 Hooks?
- 新项目或新组件:对于新的项目或组件,建议优先考虑使用 Hooks,因为它们提供了更加简洁和直观的API。
- 类组件的现有项目:如果你在一个使用大量类组件的项目中,使用 HOC 可能更容易集成,尤其是当你不打算立即迁移到函数组件时。
- 库和第三方集成:检查你所使用的第三方库或工具。它们可能已经提供了 HOC 或 Hooks,选择与库相匹配的方法会更加方便。
- 复用逻辑:如果你正在尝试复用某些逻辑,并且希望这段逻辑与特定的 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 在一些方面的比较:
- 复用逻辑:在之前,我们常常使用 HOC 来复用组件之间的逻辑。现在,相同的功能可以通过自定义 Hooks 来实现,代码更加简洁和直观。
- 渲染劫持:HOC 可以修改或增加渲染的 JSX,Hooks 则不能直接做到这一点。但通常情况下,你可以通过组合不同的组件和 Hooks 来达到相同的效果。
- 静态方法:如果你的组件有静态方法,HOC 可能会使这些方法丢失,除非你手动将它们拷贝到新组件。而 Hooks 在这方面没有这个问题,因为 Hooks 只在函数组件的主体中使用。
- Props 注入:HOC 可以向被包装的组件注入 props。这有时可能会导致 props 名称冲突或不可预期的 props 更改。Hooks 不会自动注入 props,但可以返回你想要传递给组件的任何值。
- TypeScript 和 Flow:在静态类型检查器,如 TypeScript 或 Flow 中,为 HOC 型态推导可能会有些复杂。使用 Hooks 和函数组件则相对更容易。
- 组件层级:每次使用 HOC,你都会在组件树中添加一个新的层级,这可能会使调试工具中的组件层次结构变得复杂。Hooks 不会增加组件层次。
尽管 Hooks 在很多情况下提供了更加简洁和直观的方法来复用和管理组件逻辑,但 HOC 在某些特定情况下仍然有它的价值。例如,与某些第三方库的集成可能仍然需要使用 HOC。
总的来说,虽然 Hooks 在许多常见的用例中可以替代 HOC,但根据你的具体需求和场景,选择最合适的方法仍然很重要。
HOC 例子
高阶组件 (Higher-Order Components, HOC) 是 React 中用于复用组件逻辑的一种模式。一个高阶组件接受一个组件并返回一个新组件。以下是一些经典的 HOC 例子:
- withLoading - 根据某个条件显示加载指示器或实际内容。
1function withLoading(Component) {
2 return function EnhancedComponent({ isLoading, ...props }) {
3 if (isLoading) {
4 return <div>Loading...</div>;
5 }
6 return <Component {...props} />;
7 };
8}
使用:
1const ListWithLoading = withLoading(List);
2<ListWithLoading isLoading={dataLoading} data={data} />
- withErrorBoundary - 提供错误边界功能,捕获子组件树中的 JavaScript 错误。
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}
使用:
1const SafeComponent = withErrorBoundary(MyComponent);
- withUser - 注入当前用户信息。
1function withUser(Component) {
2 return function EnhancedComponent(props) {
3 const user = getCurrentUser();
4 return <Component {...props} user={user} />;
5 };
6}
使用:
1const UserAwareComponent = withUser(SomeComponent);
- withRouter - 来自
react-router-dom
的 HOC,为你的组件提供路由属性(如history
,location
和match
)。
1import { withRouter } from 'react-router-dom';
2
3function ComponentNeedingRouterProps({ history, location, match }) {
4 // 使用 history, location, match 作一些操作
5}
6
7export default withRouter(ComponentNeedingRouterProps);
- connect - 来自
react-redux
的 HOC,用于将 Redux store 与 React 组件连接起来。
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 允许你提取这些重复的逻辑到单独的函数中,这样其他组件可以复用它而不需要重复代码。
- 使用 API 数据
缘由: 如果你的应用中有多个组件都需要从某个 API 获取数据,你可能会发现自己重复编写相同的状态管理和数据提取逻辑。
自定义 Hook:
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}
使用:
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}
- 监听窗口大小
缘由: 在响应式设计中,你可能需要根据窗口大小来更改组件的行为或样式。你可能会在多个地方需要这种逻辑。
自定义 Hook:
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}
使用:
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 结构:
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:
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
,通常涉及到以下几个步骤:
- 用一个队列(数组)来存储待处理的任务。
- 用一个队列来存储正在执行的任务。
- 当正在执行的任务数量少于指定的并发限制时,从待处理的任务队列中取出任务并开始执行。
- 每当有任务完成(无论是成功还是失败),从正在执行的任务队列中移除该任务,并检查是否可以开始下一个任务。
- 如果有待处理的任务,继续从步骤3开始执行。
下面是一个基于上述思路的 Schedule
类的实现:
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。