说说你对 React 的理解,有哪些特性?
React 是一个构建用户界面的 js 库,只提供了 ui 层面的解决方案.遵循组件设计模式、声明式编程范式和函数式编程概念,使用虚拟 Dom 操作 DOM,遵循从高阶组件到低阶的单向数据流.通过数据来控制视图.
特性:
JSX 语法
单向数据绑定
虚拟 DOM
声明式编程
Component:声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做.它表达逻辑而不显式地定义步骤
可组合:每个组件易于和其它组件一起使用,或者嵌套在另一个组件内部
可重用:每个组件都是具有独立功能的,它可以被使用在多个 UI 场景
可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护
优势
高效灵活
声明式的设计,简单使用
组件式开发,提高代码复用率
单向响应的数据流会比双向绑定的更安全,速度更快
Real DOM 和 vDOM 的区别?优缺点?
虚拟 DOM 不会进行排版与重绘操作,而真实 DOM 会频繁重排与重绘
虚拟 DOM 的总损耗是“虚拟 DOM 增删改+真实 DOM 差异增删改+排版与重绘”,真实 DOM 的总损耗是“真实 DOM 完全增删改+排版与重绘”
优缺点
真实 DOM 的优势:
易用
缺点:
效率低,解析速度慢,内存占用量过高
性能差:频繁操作真实 DOM,易于导致重绘与回流
使用虚拟 DOM 的优势如下:
简单方便:如果使用手动操作真实 DOM 来完成页面,繁琐又容易出错,在大规模应用下维护起来也很困难
性能方面:使用 Virtual DOM,能够有效避免真实 DOM 数频繁更新,减少多次引起重绘与回流,提高性能
跨平台:React 借助虚拟 DOM,带来了跨平台的能力,一套代码多端运行
缺点:
在一些性能要求极高的应用中虚拟 DOM 无法进行针对性的极致优化
首次渲染大量 DOM 时,由于多了一层虚拟 DOM 的计算,速度比正常稍慢
React 生命周期
React 生命周期分为 3 个阶段:
创建阶段,更新阶段,卸载阶段;
React 生命周期创建阶段
constructor:通过 super 关键字获取 props.一般在该方法中初始化 state 和挂载 this 上的方法;
getDerivedStateFromProps(新增):React 生命周期的静态方法不能访问到 React 的实例;执行时机为组件创建和更新阶段,传入两个参数即将更新的 props 和上一阶段的 state,该方法需要返回一个对象作为新的 state,返回 null 表示 state 无需更新;
render:类组件必须实现的方法,用于渲染 DOM 结构,可以访问 props 和 state;不要在 rander 中 setState,可能会发生死循环;
componentDidMount:组件挂载到真实 DOM 后执行,在 render 后执行;
React 更新阶段
getDerivedStateFromProps:同上;
shouldComponentUpdate:用于告知组件基于当前的 state 和 props,是否需要更新,默认返回 true;执行时机:有新的 props 和 state 都会调用,返回 true 和 false 表示组件更新与否;不建议在此进行深层比较会影响性能,同样不能做 setstate 操作;
render:同上;
getSnapshotBeforeUpdate(新增):在 render 之后执行,但是 DOM 节点还没被更新;该方法会返回一个 snapshot 的值传入 ComponentDidUpdate.该方法用于获取组件更新前的信息;
componentDidUpdate:组件更新后触发;
React 卸载阶段
componentWillUnmount:组件卸载前,用于清理注册的事件或定时器的方法等;
state 和 props 有什么区别?
相同点:
两者都是 JavaScript 对象
两者都是用于保存信息
props 和 state 都能触发渲染更新
区别:
props 是外部传递给组件的,而 state 是在组件内被组件自己管理的,一般在 constructor 中初始化
props 在组件内部是不可修改的,但 state 在组件内部可以进行修改
state 是多变的、可以修改
super()和 super(props)有什么区别?
在 React 中,类组件基于 ES6,所以在 constructor 中必须使用 super
在调用 super 过程,无论是否传入 props,React 内部都会将 porps 赋值给组件实例 porps 属性中
如果只调用了 super(),那么 this.props 在 super() 和构造函数结束之间仍是 undefined
setState 执行机制;
在 React 中需要通过 setState 来更新数据不可以通过this.state.name="张三",来更新数据,因为 react 不像 vue3 通过 Proxy 来监听数据的变化更新.,而 setState 方法,可以更新视图.他传入两个参数,第一个是需要更新的数据,第二个是回调函数;在 react 生命周期和 React 合成时间中 setState 是异步更新,而在原生事件和 setTimeout 方法中是同步更新;并且在生命周期和合成事件中更新同一个值会以最后一次的更新为主,覆盖前面的更新,并不会累加.如果需要累加,可以在 setState 的第一个参数传入一个函数进行累加更新,也可以在原生事件和 setTimeout 中更新;
onClick = () => {
this.setState((prevState, props) => {
return {count: prevState.count + 1};
});
this.setState((prevState, props) => {
return {count: prevState.count + 1};
});
}
React 的事件机制
React 基于浏览器的事件机制模拟了一套事件注册,事件冒泡,事件合成,统称合成事件.虽然表面上看与原生事件基本相同,但是他们存在着一些区别:事件的函数命名和书写方式不同,合成事件看似绑定在 Dom 上,但是它其实是绑定在了结构的最外层(不是真实的 DOM),通过一个统一的事件去监听.当组件销毁和挂载时,进行删除和插入,当事件触发时,通过这个统一的事件映射到实际触发的函数上,触发.简化了事件处理和事件回收.
阻止事件冒泡
阻止合成事件间的冒泡,用 e.stopPropagation()
阻止合成事件与最外层 document 上的事件间的冒泡,用 e.nativeEvent.stopImmediatePropagation()
阻止合成事件与除最外层 document 上的原生事件上的冒泡,通过判断 e.target 来避免
总结
React 事件机制总结如下:
React 上注册的事件最终会绑定在 document 这个 DOM 上,而不是 React 组件对应的 DOM(减少内存开销就是因为所有的事件都绑定在 document 上,其他节点没有绑定事件)
React 自身实现了一套事件冒泡机制,所以这也就是为什么我们 event.stopPropagation()无效的原因。
React 通过队列的形式,从触发的组件向父组件回溯,然后调用他们 JSX 中定义的 callback
React 有一套自己的合成事件 SyntheticEvent
React 组建通信
父组件向子组件传递:父组件在调用子组件的时候,只需要在子组件标签内传递参数,子组件通过 props 属性就能接收父组件传递过来的参数;
function EmailInput(props) {
return (
<label>
Email: <input value={props.email} />
</label>
);
}
const element = <EmailInput email="123124132@163.com" />;
子组件向父组件传递:子组件向父组件通信的基本思路是,父组件向子组件传一个函数,然后通过这个函数的回调,拿到子组件传过来的值;
//父组件
class Parents extends Component {
constructor() {
super();
this.state = {
price: 0
};
}
getItemPrice(e) {
this.setState({
price: e
});
}
render() {
return (
<div>
<div>price: {this.state.price}</div>
{/* 向子组件中传入一个函数 */}
<Child getPrice={this.getItemPrice.bind(this)} />
</div>
);
}
}
//子组件
class Child extends Component {
clickGoods(e) {
// 在此函数中传入值
this.props.getPrice(e);
}
render() {
return (
<div>
<button onClick={this.clickGoods.bind(this, 100)}>goods1</button>
<button onClick={this.clickGoods.bind(this, 1000)}>goods2</button>
</div>
);
}
}
兄弟组件之间的通信:如果是兄弟组件之间的传递,则父组件作为中间层来实现数据的互通,通过使用父组件传递;
父组件向后代组件传递:使用 context 提供了组件之间通讯的一种方式,可以共享数据,其他数据都能读取对应的数据.通过使用 React.createContext 创建一个 context
const PriceContext = React.createContext('price')
context 创建成功后,其下存在 Provider 组件用于创建数据源,Consumer 组件用于接收数据,使用实例如下:
Provider 组件通过 value 属性用于给后代组件传递数据:
<PriceContext.Provider value={100}>
</PriceContext.Provider>
如果想要获取 Provider 传递的数据,可以通过 Consumer 组件或者或者使用 contextType 属性接收,对应分别如下:
// contextType
class MyClass extends React.Component {
static contextType = PriceContext;
render() {
let price = this.context;
/* 基于这个值进行渲染工作 */
}
}
//Consumer 组件
<PriceContext.Consumer>
{ /*这里是一个函数*/ }
{
price => <div>price:{price}</div>
}
</PriceContext.Consumer>
非关系组件传递:redux;
React 中的 key 有什么作用?
key 应该是唯一的
key 不要使用随机值(随机数在下一次 render 时,会重新生成一个数字)
使用 index 作为 key 值,对性能没有优化