1、什么是React
React 是一个声明式,高效且灵活的用于构建用户界面的 JavaScript 库。主要用于构建UI,本质是前端组件化框架,不是一个完整的MVC框架,很多人认为Reatc是MVC中的View(视图)(React是一个js库,不是一个框架。跟Vue有区别
)
我们认为,React 是用 JavaScript 构建快速响应的大型 Web 应用程序的首选方式
。它在 Facebook 和 Instagram 上表现优秀。
2、React优缺点
(1)优点:
1)声明式设计
2)高效:通过对DOM的模拟,最大限度的减少与DOM的交互。
3)灵活:可以与已知的框架或库很好的配合。
4)JSX:是js语法的扩展,不一定使用,但建议用。
5)组件:构建组件,使代码更容易得到复用,能够很好地应用在大项目的开发中。
6)单向响应的数据流:React实现了单向响应的数据流,从而减少了重复代码,这也是解释了它为什么比传统数据绑定更简单。
3、React的应用
(1)React的生命周期
(2)React官方文档FAQ
1) React不是必须使用JSX
每个 JSX 元素只是调用 React.createElement(component, props, …children) 的语法糖。
//用 JSX 编写的代码
class Hello extends React.Component {
render() {
return <div>Hello {this.props.toWhat}</div>;
}
}
ReactDOM.render(
<Hello toWhat="World" />,
document.getElementById('root')
);
//不使用 JSX 的代码
class Hello extends React.Component {
render() {
return React.createElement('div', null, `Hello ${this.props.toWhat}`);
}
}
ReactDOM.render(
React.createElement(Hello, {toWhat: 'World'}, null),
document.getElementById('root')
);
2)React不是必须使用ES6
//es6
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
//es5 使用 create-react-class 模块
var createReactClass = require('create-react-class');
var Greeting = createReactClass({
render: function() {
return <h1>Hello, {this.props.name}</h1>;
}
});
3)传递函数给组件
注意:在 render 方法中使用箭头函数也会在每次组件渲染时创建一个新的函数,这会破坏基于恒等比较的性能优化。
//方法一:
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick.bind(this)}>Click Me</button>;
}
}
//方法二:
class Foo extends Component {
handleClick() {
console.log('Click happened');
}
render() {
return <button onClick={() => this.handleClick()}>Click Me</button>;
}
}
//可以改用这种方式
class Foo extends Component {
// Note: this syntax is experimental and not standardized yet.
handleClick = () => {
console.log('Click happened');
}
render() {
return <button onClick={this.handleClick}>Click Me</button>;
}
}
4)组件状态
-
setState 实际做了什么?state更新后对组件重新渲染
-
调用 setState 其实是异步的 —— 不要指望在调用 setState 之后,this.state 会立即映射为新的值,可以尝试传递一个函数而非对象
//错误 incrementCount() { // 注意:这样 *不会* 像预期的那样工作。 this.setState({count: this.state.count + 1}); } //正确 incrementCount() { this.setState((state) => { // 重要:在更新的时候读取 `state`,而不是 `this.state`。 return {count: state.count + 1} }); } //在state更新完后,执行其他操作 incrementCount() { this.setState({ aa:'1233' },() => { //xxxx }); }
-
如果 Parent 和 Child 在同一个 click 事件中都调用了 setState ,这样就可以确保 Child 不会被重新渲染两次。
取而代之的是,React 会将该 state “冲洗” 到浏览器事件结束的时候,再统一地进行更新 -
setState队列:为了合并setState,我们需要一个队列来保存每次setState的数据,然后在一段时间后执行合并操作和更新state,并清空这个队列,然后渲染组件。
5)样式与 CSS
可以在 React 中实现动画效果:参见 React Transition Group、React Motion 以及 React Spring 等示例
6)Virtual DOM 及内核
-
Shadow DOM 和 Virtual DOM 是一回事吗?
Shadow DOM 是一种浏览器技术,主要用于在 web 组件中封装变量和 CSS。Virtual DOM 则是一种由 Javascript 类库基于浏览器 API 实现的概念 -
Virtual DOM:DOM是js的api,虚拟dom是js对象
DOM:DOM是浏览器中的概念,用js对象表示页面上的元素,并提供操作DOM对象的API,每次DOM操作会引起重绘或者回流虚拟DOM:一个JS对象(数据+JXS模板),用一个js对象来描述真实的DOM
(3)diff算法指的就是两个虚拟DOM作比对;(注意不是真实DOM和虚拟DOM作对比)
虚拟DOM和react中的diff算法总结
1)传统diff算法:
使用传统的diff算法进行节点的循环遍历,复杂度是 O(n^3)。
就是逐层比较的算法,一旦发现节点消失,就删除,一旦发现新节点,就创建,发现相同的,就保留。
缺点:
(1)两棵树变化非常陡峭,是低效的。
(2)兄弟节点排序和插入新节点是低效的。
-
React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
-
React 通过分层求异的策略,对 tree diff 进行算法优化,新旧两颗DOM树,同层对比,当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。
-
React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化。如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可,不同则替换整个组件下的所有子节点。
对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切的知道这点那可以节省大量的 diff 运算时间,因此 React 允许用户通过 shouldComponentUpdate() 来判断该组件是否需要进行 diff。
-
React 通过设置唯一 key的策略,对 element diff 进行算法优化,在进行组件对比的时候,key值存在则只进行移动操作,不存在则进行添加、删除
注意:v-for数组时不建议key为index,因为当数组内容更新时,key对应的index就发生改变,以至于对应的数据跟原来的不一样
(4)MVVM 的变化检查是数据层面的,而 React 的检查是 DOM 结构层面的
网上都说操作真实 DOM 慢,但测试结果却比 React 更快,为什么
(5)React 单向数据流(也叫单向绑定)
React 中的数据流是单向的,并顺着组件层级从上往下传递
React 哲学
(6)react也有响应式原理
如何理解react响应式
用户使用setState等方法去更新状态,react会通过虚拟dom去重新渲染页面
(7)高阶函数:传入函数传出函数;高阶组件:传入组件传出组件
1)高阶组件是参数为组件,返回值为新组件的函数。HOC是纯函数,没有副作用。HOC在React的第三方库中很常见,例如Redux的connect组件。
高阶组件的作用:
- 代码复用,逻辑抽象,抽离底层准备(bootstrap)代码
- 渲染劫持
- State 抽象和更改
- Props 更改
(8)创建组件
1)函数式定义无状态组件
- 组件不会被实例化,整体渲染性能得到提升
- 组件不能访问this对象
- 组件无法访问生命周期的方法
- 无状态组件只能访问输入的props,同样的props会得到同样的渲染结果,不会有副作用
- 函数组件看似只是一个返回值是DOM结构的函数,其实它的背后是无状态组件的思想。
//hooks也是使用函数式
function HelloComponent(props){
return <div>hello {props.name}</div>
}
ReactDom.render(<HelloComponent name='haha'/>, mountNode)
2)es5原生方式React.createClass定义的组件
var InputControlES5 = React.createClass({
propTypes: {//定义传入props中的属性各种类型
initialValue: React.PropTypes.string
},
defaultProps: { //组件默认的props对象
initialValue: ''
},
// 设置 initial state,ES6可以在constructor里设置state
getInitialState: function() {//组件相关的状态对象
return {
text: this.props.initialValue || 'placeholder'
};
},
handleChange: function(event) {
this.setState({ //this represents react component instance
text: event.target.value
});
},
render: function() {
return (
<div>
Type something:
<input onChange={this.handleChange} value={this.state.text} />
</div>
);
}
});
InputControlES6.propTypes = {
initialValue: React.PropTypes.string
};
InputControlES6.defaultProps = {
initialValue: ''
};
3) es6形式的extends React.Component定义的组件
class HelloMessage extends React.Component {
constructor(props) {
super(props);
}
state = {
val: "111",
};
render() {
return <div>{this.props.name || ''}</div>;
}
}
ReactDOM.render(<HelloMessage name="Runoob"/>, mountNode);
4) 在constructor调用 super(props) 的目的是什么
constructor( )——构造方法
super( ) ——继承
-
super作为函数调用时,代表调用父类的构造函数。es6要求子类的构造函数必须执行一次super函数,这是必须的,否则js引擎会报错。
-
在super()调用之前,子类是不能使用this的。在es5中,子类必须在constructor中调用super(),传递props给super()。传递props给super()的原因则是便于在子类中能在constructor访问this.props
如果你用到了constructor就必须写super(),是用来初始化this的,可以绑定事件到this上;
如果你在constructor中要使用this.props,就必须给super加参数:super(props);
如何理解react中的super(),super(props) -
如果子类没有定义constructor方法,这个方法会被默认添加
Class 的继承class ColorPoint extends Point { } // 等同于 class ColorPoint extends Point { constructor(...args) { super(...args); } }
(9)什么是JSX
- JSX 是JavaScript XML 的简写
- 是 React 使用的一种文件,它利用 JavaScript 的表现力和类似 HTML 的模板语法
- 文件能使应用非常可靠,并能够提高其性能
- 为什么浏览器无法读取JSX?
- 浏览器只能处理 JavaScript 对象,而不能读取常规 JavaScript 对象中的 JSX
- 所以为了使浏览器能够读取 JSX,首先,需要用像 Babel 这样的 JSX 转换器将 JSX 文件转换为 JavaScript 对象,然后再将其传给浏览器。
(10)解释 React 中 render() 的目的
- 每一个react组件强制要求必须有一个render()
- 此函数必须保持纯净,即必须每次调用都返回相同结果
- 它返回一个 React 元素,是原生 DOM 组件的表示。如果需要渲染多个 HTML 元素,则必须将它们组合在一个封闭标记内,例如 form、group、div 等
(11)纯组件
-
React提供了一种自带props和state浅比较模式来确定是否应该重新渲染组件的类React.PureComponent,通常只需要继承React.PureComponent就可以定义一个纯组件
-
如果定义了 shouldComponentUpdate(),无论组件是否是 PureComponent,它都会执行shouldComponentUpdate结果来判断是否 update。如果组件未实现 shouldComponentUpdate() ,则会判断该组件是否是 PureComponent,如果是的话,会对新旧 props、state 进行 shallowEqual 比较,一旦新旧不一致,会触发 update。
-
纯组件是可以编写的最简单、最快的组件。它们可以替换任何只有 render() 的组件。这些组件增强了代码的简单性和应用的性能
-
优点:纯组件是通过控制shouldComponentUpdate生命周期函数,减少render调用次数来减少性能损耗的
-
缺点:可能会因深层的数据不一致而产生错误的否定判断,从而shouldComponentUpdate结果返回false,界面得不到更新。
因为深浅拷贝的问题,在进行浅比较时可能会对比失败以为数据没有修改
Component和PureComponent的区别
React 的性能优化(一)当 PureComponent 遇上 ImmutableJS解决方法:
- 解构操作符
add = () => { const { data } = this.state; this.setState({ data: [...data, dataGenerate()] }) }
- mmutable
(12) React组件间传值的方法有哪些?
主要传值方法有:props、context、发布/订阅、redux。
React中组件之间的传值方法有很多,按照不同的组件间关系可以把组件传值的方法分为父子组件传值,跨级组件传值和非嵌套关系组件传值。
1)父子组件传值
- props
父组件通过props将属性和方法传递给子组件。而子组件向父组件传值需要通过回调函数触发
2)跨级组件传值
-
props:写法很复杂,耦合程度也很高。需要一层层往下传递
-
context对象:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
尤其解决需要深嵌套、传递的组件
Context
React context基本用法-
React context的局限性
- 在组件树中,如果中间某一个组件 ShouldComponentUpdate returning false 了,会阻碍 context 的正常传值,导致子组件无法获取更新。
- 组件本身 extends React.PureComponent 也会阻碍 context 的更新。
-
注意点:
- Context 应该是唯一不可变的
- 组件只在初始化的时候去获取 Context
-
-
发布/订阅( call/emit )
//A.js componentDidMount() { this.eventEmitter = emitter.addListener('call',(msg) => { this.setState({ msg }) }) } componentWillUnmount() { emitter.removeListener(this.eventEmitter) } //B.js call = () => { emitter.emit('call','It's me!) }
-
redux:state、action、reduce
(13)高阶组件:参数/返回值都是组件
1)高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧
。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
2)HOC 在 React 的第三方库中很常见,例如 Redux 的 connect 和 Relay 的 createFragmentContainer。
3)请注意,HOC 不会修改传入的组件
,也不会使用继承来复制其行为。相反,HOC 通过将组件包装在容器组件中来组成新组件。HOC 是纯函数,没有副作用
。
4)注意事项
- 不要在 render 方法中使用 HOC
- Refs 不会被传递
虽然高阶组件的约定是将所有 props 传递给被包装组件,但这对于 refs 并不适用。那是因为 ref 实际上并不是一个 prop - 就像 key 一样,它是由 React 专门处理的。如果将 ref 添加到 HOC 的返回组件中,则 ref 引用指向容器组件,而不是被包装组件。
(14)性能优化
(15)Context:Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
尤其解决需要深嵌套、传递的组件 。Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。
React context基本用法
-
React context的局限性
- 在组件树中,如果中间某一个组件 ShouldComponentUpdate returning false 了,会阻碍 context 的正常传值,导致子组件无法获取更新。
- 组件本身 extends React.PureComponent 也会阻碍 context 的更新。
-
注意点:
Context 应该是唯一不可变的
- 组件只在初始化的时候去获取 Context
(16)合成事件
SyntheticEvent(合成事件) 实例将被传递给你的事件处理函数,它是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外,它还拥有和浏览器原生事件相同的接口,包括 stopPropagation() 和 preventDefault()。
个人理解为,将浏览器有的监听事件如:click、keydown等进行一个兼容不同浏览器以及性能优化的处理,在react里,开发可以直接使用onClick这样的方式进行相同的操作
Hook
1)hooks解决了什么:
- 组件之间复用状态逻辑
- 相比之前一个生命周期可以处理多个不想关的业务逻辑,hook更希望用useEffect处理相关事件
2)hooks获取models层State、Action
使用useSelector useDispatch 替代connect
//获取state数据
import React from 'react'
import { useSelector } from 'react-redux'
export const CounterComponent = () => {
const counter = useSelector(state => state.counter)
return <div>{counter}</div>
}
//获取action
const dispatch = useDispatch();
dispatch({
type: 'name',
payload: {
query:xx
},
})
Redux
Dva
面试题
(1)生命周期
(2)组件之间的通信
(3)性能优化
(4)高阶组件:是什么、有在项目中使用过吗
参数/返回值都是组件,不可修改参数组件的原型
(5)hook:什么时候出的,有什么特点
函数式创建组件,优化在组件之间复用状态逻辑很难的问题,使用use+State/Effect的形式,使得函数式里的数据也可响应式
其他知识
(1)在浏览器中每一次DOM操作都有可能引起浏览器的重绘或回流
什么是重排重绘,如何减少重拍重绘
浏览器的关键渲染路径
重排/回流:dom结构改变,重新渲染;此时在关键渲染路径中的 Layout 阶段
重绘:样式改变,重新渲染;此时在关键渲染路径中的 Paint 阶段
避免过多重拍重绘的方法:
-
CSS 样式尽量批量修改
-
避免使用 table 布局
-
为元素提前设置好高宽,不因多次渲染改变位置
-
尽可能只修改BFC的元素,减少对外界的影响
-
动画开始GPU加速,translate使用3D变化
2021年我的前端面试准备transform 不重绘,不回流是因为transform属于合成属性,对合成属性进行transition/animate动画时,将会创建一个合成层。这使得动画元素在一个独立的层中进行渲染。当元素的内容没有发生改变,就没有必要进行重绘。浏览器会通过重新复合来创建动画帧
(2)你对受控组件和非受控组件了解多少?
- 受控组件:一般是表单组件
受控组件是必须要有value的,value用来传入一个参数,结合onchang来控制这个参数输出
注意:value和onchange两者在受控组件中缺一不可,一旦缺少其中一个就会报错。
- React采用组件化的思想,最小的组件单位就是原生HTML元素,采用JSX的语法声明组件的调用
- React的虚拟DOM,就是一个大的组件树,从父组件层到子组件,在render函数中层层堆叠
- 从react-router v4开始,路由本身也是组件
- 各个库提供的hoc返回的也是组件,如withRouter、connect
- React中的基础数据state props的传递也是以组件为基础