React 基础
1、 HOC
Higher-Order Component,简称 HOC
React 高阶组件是一种接受 React 组件作为参数并返回新组件的函数。
高阶组件的好处
- 代码复用: 高阶组件可以帮助您在组件之间复用逻辑,从而减少代码冗余并提高开发效率。例如,您可以编写一个高阶组件来处理组件的数据获取逻辑,然后将其应用于所有需要获取数据的组件。
- 封装性: 高阶组件可以帮助您将公共逻辑封装成独立的组件,从而提高代码的可维护性和可读性。例如,您可以编写一个高阶组件来处理组件的授权逻辑,然后将其应用于所有需要授权的组件。
- 灵活性: 高阶组件可以为您提供一种灵活的方式来增强组件的功能。您可以使用高阶组件来添加新的生命周期方法、劫持事件、监控日志等。
高阶组件的示例
以下是一个简单的示例,演示如何使用高阶组件来添加日志功能:
function withLogging(Component) {
return class WithLogging extends Component {
render() {
console.log('Rendering component:', this.props);
return super.render();
}
};
}
const MyComponent = () => (
<div>Hello, world!</div>
);
const MyComponentWithLogging = withLogging(MyComponent);
<MyComponentWithLogging />
在这个示例中,withLogging 高阶组件接受一个组件作为参数并返回一个新组件。新组件包含原始组件的 render 方法,并在 render 方法之前添加了一些日志记录代码。
高阶组件的常见用法
- 状态管理: 高阶组件可以用于将状态管理逻辑与组件分离,例如使用 Redux 或 MobX。
- 数据获取: 高阶组件可以用于获取组件所需的数据,例如使用 Axios 或 fetch API。
- 访问控制: 高阶组件可以用于控制组件的访问权限,例如使用 React Router 或 Auth0。
- 表单验证: 高阶组件可以用于验证表单输入,例如使用 Formik 或 React Hook Form。
2、 React 的事件冒泡
3、 Redux 实现原理
// 模拟 createStore 函数,创建 Redux store
function createStore(reducer, initialState) {
let state = initialState;
let listeners = [];
function getState() {
return state;
}
function dispatch(action) {
state = reducer(state, action);
listeners.forEach(listener => listener());
}
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
listeners = listeners.filter(l => l !== listener);
};
}
dispatch({ type: '@@redux/INIT' });
return {
getState,
dispatch,
subscribe
};
}
// 模拟 connect 函数,连接 Redux store 和 React 组件
function connect(mapStateToProps, mapDispatchToProps) {
// 返回一个高阶组件
return function (WrappedComponent) {
return class extends React.Component {
componentDidMount() {
// 订阅 Redux store 的状态变化
this.unsubscribe = store.subscribe(this.handleChange);
}
componentWillUnmount() {
// 取消订阅 Redux store
this.unsubscribe();
}
handleChange = () => {
// 当 Redux store 的状态发生变化时,更新组件的状态
this.forceUpdate();
};
render() {
// 获取 Redux store 的状态
const state = store.getState();
// 根据 mapStateToProps 函数获取组件所需的状态
const stateProps = mapStateToProps ? mapStateToProps(state) : {};
// 根据 mapDispatchToProps 函数获取组件所需的操作
const dispatchProps = mapDispatchToProps ? mapDispatchToProps(store.dispatch) : {};
// 合并 stateProps 和 dispatchProps,传递给 WrappedComponent
return <WrappedComponent {...this.props} {...stateProps} {...dispatchProps} />;
}
};
};
}
// 示例的 Redux reducer
function counterReducer(state = { count: 0 }, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
// 创建 Redux store
const store = createStore(counterReducer);
// React 组件
class Counter extends React.Component {
render() {
return (
<div>
<p>Count: {this.props.count}</p>
<button onClick={this.props.increment}>Increment</button>
<button onClick={this.props.decrement}>Decrement</button>
</div>
);
}
}
// 连接 Counter 组件到 Redux store
const ConnectedCounter = connect(
// mapStateToProps 函数,将 Redux store 的状态映射到组件的 props 上
state => ({
count: state.count
}),
// mapDispatchToProps 函数,将操作映射到组件的 props 上
dispatch => ({
increment: () => dispatch({ type: 'INCREMENT' }),
decrement: () => dispatch({ type: 'DECREMENT' })
})
)(Counter);
// 渲染应用
ReactDOM.render(
<Provider store={store}>
<ConnectedCounter />
</Provider>,
document.getElementById('root')
);
4、 React 生命周期
组件的生命周期可分成三个状态:
- Mounting(挂载):已插入真实 DOM
- Updating(更新):正在被重新渲染
- Unmounting(卸载):已移出真实 DOM
4.1、 挂载
当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor: 在 React 组件挂载之前,会调用它的构造函数。getDerivedStateFromProps: 用途:让组件在 props 变化时更新 state。在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。render: render() 方法是 class 组件中唯一必须实现的方法。componentDidMount: 在组件挂载后(插入 DOM 树中)立即调用。
render() 方法是 class 组件中唯一必须实现的方法,其他方法可以根据自己的需要来实现。
4.2、 更新
每当组件的 state 或 props 发生变化时,组件就会更新。
当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
getDerivedStateFromProps: 在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。shouldComponentUpdate:当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。render: render() 方法是 class 组件中唯一必须实现的方法。getSnapshotBeforeUpdate: 在最近一次渲染输出(提交到 DOM 节点)之前调用。componentDidUpdate: 在更新后会被立即调用。
4.3、 卸载
当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount: 在组件卸载及销毁之前直接调用。
4.4、 父子组件生命周期
class A extends React.Component {
render() {
<B />
}
}
初始化阶段:
A constructor
A getDerivedStateFromProps
A render
B constructor
B getDerivedStateFromProps
B componentDidMount
A componentDidMount
更新阶段:
A getDerivedStateFromProps
A shouldComponentUpdate
A render
B getDerivedStateFromProps
B shouldComponentUpdate
A getSnapshotBeforeUpdate
A componentDidUpdate
4.5、 react16.7 废弃了几个 生命周期
- componentWillmount
- componentWillUpdate
- componentWillReceiveProps
引入了 getDerivedStateFromProp。
getDerivedStateFromProp就是一个static函数,在里面拿不到this也无法setState,更符合纯函数的概念
5、 子组件向父组件传值
import React, { PureComponent } from 'react';
export default class Children extends PureComponent {
onChange = (newText) => {
this.setState({ text: newText });
};
render(){
return(
<div>
<div>{this.state.text}</div>
<Button handleData={this.onChange}> 向父组件传值 </Button>
</div>
)
}
}
import React, { PureComponent } from 'react';
export default class Children extends PureComponent {
onClick = () => {
this.props.handleData('text2');
};
render(){
return(
<div>
<h2 onClick={this.onClick}>onClick</h2>
</div>
)
}
}
6、 父组件调用子组件的方法
通过refs:在子组件上声明一个ref值,那么在需要调用时,使用this.refs的方式即可调用子组件的方法。
class App extends React.Component {
constructor() {
super();
this.ref = React.createRef();
}
onClick = () => {
// this.ref.onShow();
this.ref.current.onShow();
}
render() {
return <div>
<h1 onClick={this.onClick}>hello react</h1>
<B ref={this.ref} />
</div>;
}
}
class B extends React.Component {
onShow = () => {
console.log(1);
};
render() {
return null;
}
}
7、 React 原理
7.1、 虚拟 DOM
虚拟 DOM 是 React 组件在内存中生成的一个对象,它描述了 DOM 节点。
比如 DOM 中的节点是这样的
<ul id="list">
<li class="item">A</li>
<li class="item">B</li>
<li class="item">C</li>
</ul>
虚拟 DOM 节点是这样的:
let oldVDOM = {
children: [
{
tagName: 'li', props: { class: 'item' }, children: ['A']
},
{
tagName: 'li', props: { class: 'item' }, children: ['B']
},
{
tagName: 'li', props: { class: 'item' }, children: ['C']
},
]
}
然后我修改了 DOM 节点中的内容,比如把 A 修改为 D,那么虚拟 DOM 节点就会变成这样:
let oldVDOM = {
children: [
{
tagName: 'li', props: { class: 'item' }, children: ['D']
},
{
tagName: 'li', props: { class: 'item' }, children: ['B']
},
{
tagName: 'li', props: { class: 'item' }, children: ['C']
},
]
}
7.2、 Diff算法
Diff算法是一种对比算法。对比两者是旧虚拟DOM和新虚拟DOM,对比出是哪个虚拟节点更改了,并只更新这个虚拟节点所对应的真实节点
7.2.1、 diff 原则
新旧虚拟DOM对比的时候,Diff算法比较只会在同层级进行, 不会跨层级比较。 所以Diff算法是:深度优先算法。 时间复杂度:O(n)

7.2.2、 component diff
React 是基于组件构建应用的,对于组件间的比较所采取的策略也是非常简洁、高效的。
- 如果是 同一类型的组件,按照原策略继续比较 Virtual DOM 树即可。
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那么就可以节省大量的 diff 运算时间。因此,React允许用户通过
shouldComponentUpdate()来判断该组件是否需进行diff算法分析,但是如果调用了forceUpdate方法,shouldComponentUpdate则失效。
7.2.3、 element diff
当节点处于同一层级时,diff 提供了 3 种节点操作:插入、移动、删除。
- 插入:新的组件类型不在旧集合里,即全新的节点,需要对新节点执行插入操作
- 移动:旧集合中有新组件类型,且 element 是可更新的类型
- 删除:旧集合中存在新组件类型,但 element 不再需要。或者旧组件不在新集合里
8、 Vue $nextTick VS React setState Callback
Vue 中 $nextTick 的实现:
- 在 Vue 中,当数据发生变化时,Vue 会将更新 DOM 的操作放入微任务队列(Microtask Queue)中,而不是直接更新 DOM。
$nextTick会将回调函数也放入微任务队列中,确保在 DOM 更新完成后再执行回调函数。- Vue 会先尝试使用原生的
Promise.resolve().then()来实现微任务,如果浏览器不支持 Promise,则会使用MutationObserver或setImmediate等备选方案。
export default {
name: 'Test',
data () {
return {
msg:"hello world",
}
},
methods: {
changeMsg() {
this.msg = "hello Vue" // vue数据改变,改变了DOM里的innerText
let msgEle = this.$refs.msg.innerText //后续js对dom的操作
console.log(msgEle) // hello world
// 输出可以看到data里的数据修改后DOM并没有立即更新,后续的DOM不是最新的
this.$nextTick(() => {
console.log(this.$refs.msg.innerText) // hello Vue
})
this.$nextTick().then(() => {
console.log(this.$refs.msg.innerText) // hello Vue
})
},
}
}
React 的 setState 是在状态更新后立即执行。但是,由于 React 的状态更新是异步的,因此在回调函数执行时,DOM 可能尚未完成更新。