1. UI 编程痛点
UI 编程的痛点通常集中在以下几个方面:
a. 组件之间的通信
- 父子组件传值:React 通过 props 在父组件和子组件之间传递数据,但当组件层级较深时,父子组件传递数据变得冗长且不易管理。
- 跨层级或兄弟组件通信:通过 props 无法直接跨越多个组件层级传递数据,因此需要使用 状态提升 或 上下文(Context) 等技术,这可能导致组件间的耦合增加。
b. 状态管理
- 全局状态和局部状态的平衡:当应用变得复杂时,状态管理变得困难。React 允许使用 useState 和 useReducer 管理组件状态,但当多个组件需要共享同一状态时,需要使用 Redux 或 Context API。如何选择合适的状态管理工具,并合理拆分状态,避免过度使用全局状态或过度嵌套的组件结构,是一个常见的痛点。
c. 性能优化
- 组件重渲染:当状态或 props 更新时,React 会触发组件的重新渲染,过度渲染会影响性能。尤其是在大型应用中,频繁的渲染可能导致页面卡顿。
- 使用 React.memo、useMemo、useCallback 来避免不必要的渲染。
- 使用 Lazy loading 和 Suspense 进行懒加载,避免初次加载时过多的组件渲染。
2. 响应式与转换式
在现代 UI 开发中,响应式编程和转换式编程是两种常见的思想。
a. 响应式编程(Reactive Programming)
响应式编程是通过对数据的变化进行监听和反应来创建动态 UI,数据源和 UI 之间的依赖关系是自动维护的。在 React 中,组件的渲染依赖于状态(state)和属性(props),这就是响应式的体现。
React 与响应式编程:
- React 的数据流遵循 单向数据流:状态和 props 的变化会自动触发 UI 更新。React 会自动根据 state 的变化重新渲染组件。
- 通过 Hooks(如
useState、useEffect)和 Context API,React 更加便于管理响应式数据流。
b. 转换式编程(Declarative Programming)
转换式编程指的是通过声明如何做,而不是具体去做某件事情。在 UI 编程中,React 鼓励使用声明式编程,通过 JSX 语法描述 UI,而非操作 DOM 元素。
React 与声明式编程:
- 使用 JSX 语法描述 UI,React 会根据状态和 props 自动处理 DOM 更新,不需要开发者直接操作 DOM。
- 例如,使用
useState来声明一个计数器的状态,React 会自动处理当状态变化时如何更新 UI。
3. React 设计与实现 - 组件化
React 推崇 组件化的设计理念,即将 UI 分解成多个小的、可复用的组件。每个组件有自己的状态、生命周期和逻辑,可以独立开发、测试和维护。
a. 组件设计原则
- 单一职责原则:每个组件只做一件事,确保组件职责明确。
- 高内聚低耦合:组件之间的依赖尽量减少,使得组件更加独立。
- 复用性:组件应该设计为可以在不同场景下复用,避免重复代码。
- 可组合性:组件应该能够和其他组件组合,形成更复杂的界面。
b. 容器组件与展示组件
- 容器组件(Container Components)负责管理应用的状态、处理业务逻辑、并将数据传递给子组件。它们通常不关注 UI 细节。
- 展示组件(Presentational Components)专注于 UI 渲染,接收通过 props 传递的数据,负责展示界面,不包含业务逻辑。
这种分离的设计方式有助于提升组件的复用性和可维护性。
4. React 设计与实现 – 状态归属问题
在 React 中,状态(state)的管理是设计中的一个核心问题。状态决定了组件的 UI 和行为,合理的状态管理是构建高效应用的基础。
a. 状态归属的挑战
- 组件内部状态:有些状态是组件内部的,只有该组件需要使用的状态(例如,表单输入、UI 切换状态等)。这些状态应该保存在组件内部。
- 状态提升:当多个组件需要共享某个状态时,需要将状态提升到它们的最近共同父组件中。
- 全局状态管理:当应用中有多个组件跨越多个层级需要共享状态时,使用 Redux、Context API 或 Recoil 等工具管理全局状态。
b. 如何决策状态的归属
- 如果某个状态只对一个组件有意义,状态应该保存在该组件内部。
- 如果多个子组件需要访问同一状态,将状态提升到它们的共同父组件。
- 对于应用级别的共享状态,考虑使用 Redux、MobX 或 Context API。
c. React 中的状态管理模式
useState和useReducer:适用于局部状态管理。useReducer适用于复杂状态逻辑的场景。- Redux / Recoil / Zustand:适用于跨组件、跨页面的全局状态管理。
5. React 设计与实现 – 生命周期
React 的组件生命周期是指组件从创建到销毁的过程,生命周期方法允许你在不同的阶段插入自定义逻辑。
a. 类组件生命周期
在 React 的类组件中,生命周期方法分为三类:
- 挂载阶段(Component Mounting):
constructor():构造函数,用于初始化状态和绑定方法。componentDidMount():组件挂载后调用,通常用于获取数据。
- 更新阶段(Component Updating):
shouldComponentUpdate():判断是否需要重新渲染组件。getSnapshotBeforeUpdate():在更新之前获取 DOM 快照。componentDidUpdate():组件更新后调用,用于处理副作用(如重新获取数据)。
- 卸载阶段(Component Unmounting):
componentWillUnmount():组件卸载前调用,用于清理副作用(如清除定时器、取消订阅等)。
b. 函数组件生命周期(通过 Hooks)
函数组件没有传统的生命周期方法,但通过 Hooks,React 提供了类似的功能:
-
useEffect:替代了类组件的componentDidMount、componentDidUpdate和componentWillUnmount,通过控制useEffect的依赖项,可以模拟组件生命周期的各种阶段。useEffect(() => { // 组件挂载时执行 return () => { // 组件卸载时清理副作用 }; }, []); // 依赖数组为空,表示仅在挂载和卸载时执行 -
useLayoutEffect:与useEffect类似,但它会在 DOM 更新后立即同步执行。适用于需要读取 DOM 布局或进行布局调整的情况。
在 TypeScript 中,组件通常指的是通过 React 或其他框架构建的可重用的 UI 单元。使用 TypeScript 来编写 React 组件能够提供类型安全,帮助开发者在编写代码时避免类型错误,增强代码的可维护性和可读性。
下面是一个基于 React 和 TypeScript 的组件实现示例,涵盖了函数式组件(Functional Component)和类组件(Class Component)的基本结构和类型声明。
1. React 函数组件 (Functional Component)
在 React 中,函数组件是最常见的组件形式。使用 TypeScript,可以为组件的 props(属性)提供类型注解。
示例:基本的函数组件
import React from 'react';
// 定义 props 的类型
interface MyComponentProps {
title: string;
isActive: boolean;
onClick: () => void;
}
// 函数组件
const MyComponent: React.FC<MyComponentProps> = ({ title, isActive, onClick }) => {
return (
<div>
<h1>{title}</h1>
<p>{isActive ? 'Active' : 'Inactive'}</p>
<button onClick={onClick}>Click Me</button>
</div>
);
};
export default MyComponent;
解释:
interface MyComponentProps:定义了该组件接收的 props 类型。它包含了title(字符串类型)、isActive(布尔类型)和onClick(一个函数类型,表示点击时的事件处理函数)。React.FC:React.FC(Functional Component 的缩写)是 React 提供的一个泛型类型,它为组件类型提供了内置的类型推断支持,能够自动推断children的类型。{ title, isActive, onClick }:组件通过解构的方式接收props,并在 JSX 中使用。
2. React 类组件 (Class Component)
类组件是 React 中较早的组件定义方式,虽然函数组件(结合 hooks)已成为主流,但类组件仍然在一些代码库中广泛使用。
示例:基本的类组件
import React, { Component } from 'react';
// 定义 props 和 state 的类型
interface MyComponentProps {
title: string;
isActive: boolean;
}
interface MyComponentState {
count: number;
}
class MyComponent extends Component<MyComponentProps, MyComponentState> {
constructor(props: MyComponentProps) {
super(props);
this.state = {
count: 0
};
}
handleClick = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
const { title, isActive } = this.props;
const { count } = this.state;
return (
<div>
<h1>{title}</h1>
<p>{isActive ? 'Active' : 'Inactive'}</p>
<button onClick={this.handleClick}>Increment</button>
<p>Count: {count}</p>
</div>
);
}
}
export default MyComponent;
解释:
MyComponentProps和MyComponentState:分别定义了组件的props和state类型。MyComponentProps包含组件所需的属性(title和isActive),MyComponentState定义了组件的状态(count)。constructor(props: MyComponentProps):类组件的构造函数,用于初始化组件的状态。通过super(props)调用父类的构造函数。render():render()方法是类组件中必须实现的,用于渲染组件的 UI。
3. 使用 TypeScript 和 React Hooks
在现代 React 开发中,函数组件结合 hooks(如 useState, useEffect)已经成为常用的开发模式。下面是一个使用 useState 和 useEffect hooks 的 TypeScript 组件示例。
示例:带有 Hooks 的函数组件
import React, { useState, useEffect } from 'react';
// 定义 props 类型
interface TimerProps {
initialTime: number;
}
const Timer: React.FC<TimerProps> = ({ initialTime }) => {
const [time, setTime] = useState<number>(initialTime);
useEffect(() => {
const timer = setInterval(() => {
setTime((prevTime) => prevTime - 1);
}, 1000);
return () => clearInterval(timer); // 清除定时器
}, []);
return (
<div>
<h1>Time Remaining: {time}s</h1>
{time <= 0 && <p>Time's up!</p>}
</div>
);
};
export default Timer;
解释:
useState<number>(initialTime):使用useStatehook 来创建组件的状态变量time,并设置初始值为initialTime。useState的泛型参数指定了状态的类型,这里是number类型。useEffect:useEffecthook 用于执行副作用操作(如启动定时器)。在这里,我们使用setInterval每秒钟更新一次时间。当组件卸载时,清除定时器以避免内存泄漏。
4. 类型推导与默认值
TypeScript 在 React 中还支持 类型推导 和 默认值,让组件的使用更加灵活。例如,props 可以为可选项或提供默认值:
interface ButtonProps {
label: string;
color?: string; // 可选属性
}
const Button: React.FC<ButtonProps> = ({ label, color = 'blue' }) => {
return <button style={{ backgroundColor: color }}>{label}</button>;
};
解释:
color?: string:color属性是可选的(使用?标记),表示如果没有传入color,则会使用默认的blue。color = 'blue':在函数组件的参数解构中,我们给color设置了默认值'blue'。
5. Props 和 State 的复杂类型
有时组件需要接收更复杂的 props,比如数组、对象或函数等类型。TypeScript 可以帮助我们精确地定义这些类型。
示例:接收数组和函数作为 Props
interface ListProps {
items: string[];
renderItem: (item: string) => JSX.Element;
}
const List: React.FC<ListProps> = ({ items, renderItem }) => {
return (
<div>
{items.map((item, index) => (
<div key={index}>{renderItem(item)}</div>
))}
</div>
);
};
export default List;
解释:
items: string[]:items是一个字符串数组,代表要渲染的列表项。renderItem: (item: string) => JSX.Element:renderItem是一个函数,接收一个字符串作为参数,返回一个 JSX 元素。