组件基础
事件机制
React 并不是将 click 事件绑定到真实 DOM 上,而是在 document 处监听所有事件,当事件发生并冒泡到 document 处,React 将事件内容封装并交由真正的处理函数运行 — 顶层事件代理机制。
这样做的好处是不仅能减少内存的消耗、提高性能,还能在组件挂载和销毁时统一订阅和移除事件。
除此之外冒泡到 document 上的事件也不是原生浏览器事件,而是 React 自己实现的合成事件。因此如果要阻止事件冒泡的话,需要调用 event.preventDefault 方法而不是 event.stopPropagation。
这样做的目的是:
- [兼容浏览器,跨平台] 合成事件能抹平浏览器间的兼容问题,另外这是跨浏览的原生事件包装器,赋予了跨浏览器开发的能力。
- [垃圾回收] 对原生浏览器事件来说,浏览器会给监听器创建一个事件对象。如果有多个事件监听,就需要分配多个事件对象,造成高额的内存分配问题。对合成事件而言,有专门的事件池来管理事件的创建和销毁,能够更好的复用和销毁。
- [方便管理] 便于 React 统一管理和事务机制。
事件顺序:原生事件执行,合成事件执行,document 上监听的事件。
React 高阶组件、Render props、hooks 有什么区别
这三者是目前 react 解决代码复用的主要方式:
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。具体而言,高阶组件是参数为组件,返回值为新组件的函数。
优点 ∶ 逻辑服用、不影响被包裹组件的内部逻辑。 缺点 ∶
-
名称冲突:不同的 HOC 可能会给组件添加相同的 props,这就可能会导致冲突。这种问题可以通过约定命名规范来避免,但是这种方式并不完美。
假设你有两个 HOCs,一个是 withUser,一个是 withNetwork。他们都会向被包装的组件传递一个名为 data 的 prop。
// withUser HOC function withUser(Component) { return function WrappedComponent(props) { const user = { name: 'John Doe' }; return <Component {...props} data={user} />; }; } // withNetwork HOC function withNetwork(Component) { return function WrappedComponent(props) { const network = { status: 'connected' }; return <Component {...props} data={network} />; }; }
然后你有一个 Profile 组件,你希望这个组件同时具有 withUser 和 withNetwork 的功能。
function Profile(props) { const { data } = props; return <p>{JSON.stringify(data)}</p>; } const EnhancedProfile = withUser(withNetwork(Profile));
在这里,withUser 和 withNetwork 都试图向 Profile 组件传递 data prop,但是由于他们使用了相同的名称,所以实际上 Profile 组件接收到的 data 只会是其中一个 HOC 提供的数据,另一个 HOC 提供的数据会被覆盖。这就是所谓的名称冲突。
-
不透明:HOC 在组件之间创建了一个抽象层,这使得类型信息更难以理解和追踪。此外,当我们使用 HOC 包装组件时,新的复合组件的属性会被原始组件继承,这可能会导致属性冲突。
// withLoading HOC function withLoading(Component) { return function WrappedComponent(props) { const extraProps = { message: 'Loading...' }; return <Component {...props} {...extraProps} />; }; } // withError HOC function withError(Component) { return function WrappedComponent(props) { const extraProps = { message: 'Error!' }; return <Component {...props} {...extraProps} />; }; } function DataDisplay(props) { return <p>{props.message}</p>; } const EnhancedDataDisplay = withLoading(withError(DataDisplay));
在这个例子中,”属性冲突”的问题就出现了。withLoading 和 withError 都向 DataDisplay 组件添加了 message 属性,但是由于 withLoading 是后添加的,所以 DataDisplay 组件实际上看到的 message 是 ‘Loading…’,而不是 ‘Error!’。这就是属性冲突。
-
refs 必须使用 React 提供的其他方式,如 React.forwardRef
render props 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术,更具体的说,render prop 是一个用于告知组件需要渲染什么内容的函数 prop。
优点:数据共享、代码复用,将组件内的 state 作为 props 传递给调用者,将渲染逻辑交给调用者。 缺点:
- 无法在 return 语句外访问数据
- 嵌套深度:当使用多个 render props 时,组件树的嵌套深度可能会增加,这可能导致代码难以阅读和理解。这个问题也被称为“回调地狱”。
- 性能问题:每次渲染都会创建一个新的函数,这可能会导致子组件无谓的重渲染,从而影响性能。
Hook 是 React 16.8 的新增特性。它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。通过自定义 hook,可以复用代码逻辑。
hook 的优点如下 ∶
- 使用直观;
- 解决 hoc 的 prop 重名问题;
- 解决 render props 因共享数据 而出现嵌套地狱的问题;
- 能在 return 之外使用数据的问题。
React Hooks 解决了什么问题
React Hooks 是 React 16.8 引入的一项新特性,它解决了以下几个问题:
- 在组件之间复用状态逻辑:在使用类组件时,对于复用一些状态逻辑,需要使用高阶组件或者 render props 等方式,代码会变得复杂和冗余。而使用 Hooks 可以通过自定义的 Hook 来复用状态逻辑,将相关的状态和操作都封装在一个函数中,以便在不同的组件中进行复用。
- 处理组件之间的副作用:在类组件中,副作用的处理通常在生命周期方法中进行,例如 componentDidMount、componentDidUpdate、componentWillUnmount 等。这样会导致相关的逻辑被分散在不同的生命周期方法中,不易于维护和理解。而使用 useEffect Hook 可以将组件的副作用逻辑集中在一起,使代码更加清晰和易于管理。
- 函数组件中使用状态:在 React 16.7 之前,函数组件是无状态的,无法使用类组件中的状态(state)。而使用 useState Hook 可以在函数组件中使用状态,使函数组件具备了类组件的状态管理能力。
- 简化 React 组件的编写:使用 Hooks 可以让组件的编写更加简洁和直观,不需要关注类组件中的繁琐的生命周期方法,也不需要使用 this 关键字。同时,使用函数组件代替类组件,可以减少代码量和提高性能。
总的来说,React Hooks 的出现使得组件的编写更加简单和直观,使得状态管理和副作用处理更加灵活和方便,提高了开发效率和代码的可维护性。但需要注意,Hooks 是在 React 16.8 引入的,如果项目使用的是旧版本的 React,就无法使用 Hooks。
React 16 架构
- Scheduler(调度器) – 调度任务的优先级,高优任务优先进入 Reconciler
- Reconciler(协调器) – 负责找出变化的组件
- Renderer(渲染器) – 负责将变化的组件渲染到页面上
Scheduler
当浏览器有剩余时间通知,类似 requestIdleCallback 机制
Reconciler 递归任务可中断,shouldYield 判断当前是否有剩余时间。对虚拟 DOM 打上增/删/更新的标记。
Renderer 根据 Reconciler 为虚拟 DOM 打的标记,同步执行对应的 DOM 操作。