一、组件、组件通信
1. class组件和函数组件的区别
1.1 特性
- class 组件有生命周期;
- class 组件内部有this,可以基于 this 执行多种操作;
- 函数组件能够方便提取状态逻辑,容易测试。
1.2 函数组件捕获了渲染所使用的值
解释:React 中 props 是不可变的,但this 是一直可变的。因此 class 组件每次调用this.props
时都会去获取最新的this值,无法保证当前读取到的 props 就是最初捕获到的。而函数组件读取的 props 会在函数执行时就被捕获。也就是说函数组件中 延迟输出 的state
是调用时的state
,而不是最新的state
。
class ProfilePage extends React.Component {
showMessage = () => alert('Followed ' + this.props.user);
handleClick = () => setTimeout(this.showMessage, 3000);
render() {
return <button onClick={this.handleClick}>Follow</button>;
}
}
React 框架中有一个经典的公式UI = f(data)
,也就是说 React 框架的本质就是输入数据、输出 UI,数据应该和 UI 绑定在一起。而 class 组件读取this.props
的表现会打破 state 和 UI 之间的联系,不符合 React 的理念。
(补充忽略)React
的经典公式UI = f(data)
,也就是说UI
是当前应用状态的一个函数,那么事件处理程序也是渲染结果的一部分,它应该是一个拥有特定props
和state
的特定渲染。然而对于类组件来说,调用一个回调函数读取一个 延迟的this.props
时,会打断这种关联,showMessage
回调并没有与任何一个特定的渲染绑定在一起,所以它失去了正确的props。也就是说,从this
中读取数据的这种行为,切断了这种联系。
-
怎么修复class组件的这个bug呢?可以提前读取
this.props
。 -
如果想要读取最新的props和state,而不是捕获时的数据,可以采用class组件的this.props/this.state。
1.3 性能优化
- class组件:主要靠
shouldComponentUpdate
避免不必要的渲染。 - 函数组件:通过
useMemo
、useCallback
来进行缓存以提升性能。
2. 受控组件和非受控组件:针对表单元素
值是由 React 的 state 控制的表单元素称为受控组件。值不受 React 控制的称为非受控组件。
input
等表单元素通常自己会维护一个state,然后根据用户输入进行更新。而 React 组件内部也会维护一个 state,并通过setState
来更新。我们将两者进行结合,使 React 的 state 成为唯一的数据源。
- 将 React 的state 赋值给表单元素的 value 属性。
- 用
onChange
事件监听输入,输入发生变化时就调用setState
更新state。
function App() {
const [name, setName] = useState('');
return (
<input value={name} onChange={e => setName(e.target.value)}>
);
}
3. ref 和 forwardRef
ref 是对 DOM 节点或 React 组件实例的引用。ref 属性可以让我们访问到它们,适用于管理焦点、滚动位置等场景。
import { useRef } from 'react';
function Form() {
const inputRef = useRef(null); // { current: null }
function handleClick() {
inputRef.current.focus();
}
return (
<>
<input ref={inputRef} /> { current:DOM节点的引用 }
<button onClick={handleClick}>聚焦输入框</button>
</>);
}
ref 和 state 的区别:
- ref 发生变化并不会触发组件渲染,state 改变时会触发渲染。
- ref 是可以任意修改的,而 state 只能通过 setState 修改。
React 不推荐使用 ref 将子组件实例暴露给父节点的方式。建议使用forwardRef
转发的方式实现将子组件实例暴露给父组件。
参数是一个render函数,接收父组件传递的 props 和 ref。返回React组件。
const MyInput = forwardRef(function MyInput(props, ref) {
//...
})
4. 五种组件通信的方式
-
父组件 => 子组件:
props
、forwardRef
。 -
子组件 => 父组件:
回调函数
,父组件将一个函数作为 props 传递给子组件,子组件调用该函数就可以向父组件通信。
<Sub callback = { this.callback.bind(this) } />
props.callback(msg)
-
跨层级组件
- 中间组件层层传递
props
。 - 使用
context
。
- 中间组件层层传递
-
兄弟组件:将公共的 state 向上移动到共同的父组件中。
-
不相关的复杂组件:事件总线、
redux
等状态管理库去进行全局状态管理。
二、react18 事件机制
不同浏览器对事件的兼容性不同,React 通过实现一个兼容全浏览器的事件系统来抹平浏览器之间的差异,方便对所有事件统一管理。
- 原生事件就是用
document.addEventListener
设置监听的事件,绑定在真实 dom 上。 - 合成事件像
onClick
并不会绑定到真实 dom 上,而是绑定到root节点上。(v16绑定到document)
const rootNode = document.getElementById('root');
// v17 之后使用 rootNode.addEventListener() 来进行绑定
ReactDOM.render(<App />, rootNode);
合成事件是React模拟原生 DOM 事件所有能力的一个事件对象,能够兼容所有浏览器。
原生事件和合成事件的区别
- 事件命名方式不同:合成事件采用小驼峰式,原生事件是纯小写;
- 事件处理函数写法不同:原生事件中事件处理函数为字符串,而合成事件中是传参一个函数作为事件处理函数。
- 阻止默认事件的方式不同:原生事件可以
return false
阻止,React 需要调用e.preventDefault
。
<button onclick="handleClick()">原生事件</button>
<button onClick={handleClick}>合成事件</button>
事件机制原理(???)
- 事件收集:React 对所有的事件进行了分类,并生成了一个React事件和它依赖的原生事件的集合。
- 事件绑定:初始化时会将所有事件委托到根节点上,并且会区分冒泡阶段、捕获阶段。
- 事件触发:触发时目标节点的 Fiber 节点会一直向上收集对应的事件,根据冒泡或者捕获阶段进行触发。
执行顺序
当点击 DOM 元素触发后,会先执行原生事件,再处理React合成事件,最后执行 root 节点上挂载的事件。
参考文章