1、当调用 setState的时候,发生了什么操作?
当调用 setState时, React做的第一件事是将传递给setState的对象合并到组件的当前状态,这将启动一个称为和解( reconciliation)的过程。
和解的最终目标 是:根据这个新的状态以最有效的方式更新DOM。
为此, React将构建一个新的 React虚拟DOM树(可以将其视为页面DOM元素的对象表示方式)。
一旦有了这个DOM树,为了弄清DOM是如何响应新的状态而改变的, React会将这个新树与上一个虚拟DOM树比较。
这样做, React会知道发生的确切变化,并且通过了解发生的变化后,在绝对必要的情况下进行更新DOM,即可将因操作DOM而占用的空间最小化。
2、在 React中元素( element)和组件( component)有什么区别?
简单地说,在 React中元素(虛拟DOM)描述了你在屏幕上看到的DOM元素。
换个说法就是,在 React中元素是页面中DOM元素的对象表示方式。在 React中组件是一个函数或一个类,它可以接受输入并返回一个元素。
注意:工作中,为了提高开发效率,通常使用JSX语法表示 React元素(虚拟DOM)。在编译的时候,把它转化成一个 React. createElement调用方法。
3、在构造函数调用 super 并将 props 作为参数传入的作用
- 在构造函数里调用super的目的是:保证子类(组件里)有this;
在ES6中,super()函数就是父类的构造函数。所以,在react里定义类组件时,调用 super就是在调用React.Component的构造函数。
- 调用super函数时,把props传入的目的是,保证this有属性props;即:在子类(组件)里能够使用this.props
举例:
- 传递了props
javascript
class MyComponent extends React.Component {
constructor(props) {
super(props);
console.log(this.props); // { name: 'sudheer',age: 30 }
}
}
- 没传递 props
javascript
class MyComponent extends React.Component {
constructor(props) {
super();
console.log(this.props); // undefined
// 但是 Props 参数仍然可用
console.log(props); // Prints { name: 'sudheer',age: 30 }
}
render() {
// 构造函数外部不受影响
console.log(this.props); // { name: 'sudheer',age: 30 }
}
}
4、什么是 React的refs?为什么它们很重要?
refs允许你直接访问DOM元素或组件实例。为了使用它们,可以向组件添加个ref属性。
如果该属性的值是一个回调函数,它将接受底层的DOM元素或组件的已挂载实例作为其第一个参数。可以在组件中存储它。
javascript
export class App extends Component {
showResult ( ) {
console. log(this. input. value)
}
render ( ) {
return (
<div>
<input type="text" ref={input => this .input =input } />
< button onClick={this. showResult.bind(this)}>展示结果</ button>
</div>
);
}
}
如果该属性值是一个字符串, React将会在组件实例化对象的refs属性中,存储一个同名属性,该属性是对这个DOM元素的引用。可以通过原生的 DOM API操作它。
javascript
export class App extends Component {
showResult(){
console .log ( this.refs.username.value)
}
render(){
return (
<div>
<input type="text" ref="username"/>
< button onClick={this. showResu1t.bind (this)}>展示结果</ button>
</div>
);
}
}
5、React 中的key是什么?为什么它们很重要?
key可以帮助 React跟踪循环创建列表中的虚拟DOM元素,了解哪些元素已更改、添加或删除。
每个绑定key的虚拟DOM元素,在兄弟元素之间都是独一无二的。在 React的和解过程中,比较新的虛拟DOM树与上一个虛拟DOM树之间的差异,并映射到页面中。key使 React处理列表中虛拟DOM时更加高效,因为 React可以使用虛拟DOM上的key属性,快速了解元素是新的、需要删除的,还是修改过的。如果没有key,React就不知道列表中虚拟DOM元素与页面中的哪个元素相对应。所以在创建列表的时候,不要忽略key。
6、React 性能优化在哪个生命周期?它优化的原理是什么?
react的父级组件的render函数重新渲染会引起子组件的render方法的重新渲染。但是,有的时候子组件的接受父组件的数据没有变动。子组件render的执行会影响性能,这时就可以使用shouldComponentUpdate来解决这个问题。
使用方法如下:
javascript
shouldComponentUpdate(nexrProps) {
if (this.props.num === nexrProps.num) {
return false
}
return true;
}
shouldComponentUpdate提供了两个参数nextProps和nextState,表示下一次props和一次state的值,当函数返回false时候,render()方法不执行,组件也就不会渲染,返回true时,组件照常重渲染。此方法就是拿当前props中值和下一次props中的值进行对比,数据相等时,返回false,反之返回true。
需要注意,在进行新旧对比的时候,是**浅对比,**也就是说如果比较的数据时引用数据类型,只要数据的引用的地址没变,即使内容变了,也会被判定为true。
面对这个问题,可以使用如下方法进行解决:
(1)使用setState改变数据之前,先采用ES6中assgin进行拷贝,但是assgin只深拷贝的数据的第一层,所以说不是最完美的解决办法:
javascript
const o2 = Object.assign({},this.state.obj)
o2.student.count = '00000';
this.setState({
obj: o2,
})
(2)使用JSON.parse(JSON.stringfy())进行深拷贝,但是遇到数据为undefined和函数时就会错。
javascript
const o2 = JSON.parse(JSON.stringify(this.state.obj))
o2.student.count = '00000';
this.setState({
obj: o2,
})
react性能优化是在哪个生命周期函数中
在shouldComponentUpdate 这个方法中,这个方法主要用来判断是否需要调用render方法重绘DOM
因为DOM的描绘非常消耗性能,如果能够在shouldComponentUpdate方法中能写出更优化的 diff算法,极大的提高性能
7、约束性组件( controlled component)与非约束性组件( uncontrolled component)有什么区别?
在 React中,组件负责控制和管理自己的状态。
如果将HTML中的表单元素( input、 select、 textarea等)添加到组件中,当用户与表单发生交互时,就涉及表单数据存储问题。根据表单数据的存储位置,将组件分成约東性组件和非约東性组件。
约束性组件( controlled component)就是由 React控制的组件,也就是说,表单元素的数据存储在组件内部的状态中,表单到底呈现什么由组件决定。
如下所示, username没有存储在DOM元素内,而是存储在组件的状态中。每次要更新 username时,就要调用 setState更新状态;每次要获取 username的值,就要获取组件状态值。
javascript
class App extends Component {
//初始化状态
constructor ( props ) {
super ( props )
this .state = {
username:'有课前端网'
}
}
//查看结果
showResult ( ) {
//获取数据就是获取状态值
console. log ( this .state. username )
}
changeUsername (e) {
//原生方法获取
var value =e .target .value
//更新前,可以进行脏值检测
//更新状态
this .setState ( {t
username:value
} )
}
//渲染组件
render( ) {
//返回虚拟DOM
return (
<div>
<p>
{/*输入框绑定va1ue*/}
<input type="text" onChange={ this.changeUsername .bind (this ) }
value= { this .state.username }/>
</p>
<p>
< button onClick={this.showResult.bind (this)}>查看结果</ button>
</p>
</div>
)
}
}
非约束性组件( uncontrolled component)就是指表单元素的数据交由元素自身存储并处理,而不是通过 React组件。表单如何呈现由表单元素自身决定。
如下所示,表单的值并没有存储在组件的状态中,而是存储在表单元素中,当要修改表单数据时,直接输入表单即可。有时也可以获取元素,再手动修改它的值。当要获取表单数据时,要首先获取表单元素,然后通过表单元素获取元素的值。
注意:为了方便在组件中获取表单元素,通常为元素设置ref属性,在组件内部通过refs属性获取对应的DOM元素。
javascript
class App extends Component {
//查看结果
showResult ( ) {
//获取值
console. log(this. refs. username .value)
//修改值,就是修改元素自身的值
this.refs.username.value="专业前端学习平台"
}
//渲染组件
render ( ) {
//返回虚拟DOM
return (
<div>
<p>
{/*非约束性组件中,表单元素通过 defaultvalue定义*/}
< input type="text" ref=" username" defaultvalue="有课前端网"/>
</p>
<p>
< button onClick={this. showResult.bind ( this ) }>查看结果</button>
</p>
</div>
)
}
虽然非约東性组件通常更容易实现,可以通过refs直接获取DOM元素,并获取其值,但是 React建议使用约束性组件。主要原因是,约東性组件支持即时字段验证,允许有条件地禁用/启用按钮,强制输入格式等。
8、在哪个生命周期中你会发出Ajax请求?为什么?
Ajax请求应该写在组件创建期的第五个阶段,即 componentDidMount生命周期方法中。原因如下。
在创建期的其他阶段,组件尚未渲染完成。而在存在期的5个阶段,又不能确保生命周期方法一定会执行(如通过 shouldComponentUpdate方法优化更新等)。在销毀期,组件即将被销毁,请求数据变得无意义。因此在这些阶段发岀Ajax请求显然不是最好的选择。
在组件尚未挂载之前,Ajax请求将无法执行完毕,如果此时发出请求,将意味着在组件挂载之前更新状态(如执行 setState),这通常是不起作用的。
在 componentDidMount方法中,执行Ajax即可保证组件已经挂载,并且能够正常更新组件。
9、shouldComponentUpdate有什么用?为什么它很重要?
组件状态数据或者属性数据发生更新的时候,组件会进入存在期,视图会渲染更新。在生命周期方法 should ComponentUpdate中,允许选择退出某些组件(和它们的子组件)的和解过程。
和解的最终目标是根据新的状态,以最有效的方式更新用户界面。如果我们知道用户界面的某一部分不会改变,那么没有理由让 React弄清楚它是否应该更新渲染。通过在 shouldComponentUpdate方法中返回 false, React将让当前组件及其所有子组件保持与当前组件状态相同。
10、如何用 React构建( build)生产模式?
通常,使用 Webpack的 DefinePlugin方法将 NODE ENV设置为 production。这将剥离 propType验证和额外的警告。除此之外,还可以减少代码,因为 React使用 Uglify的dead-code来消除开发代码和注释,这将大大减少包占用的空间。
可以通过配置package.json中的script中的mode为production
11、为什么要使用 React. Children. map( props. children,( )=>)而不是props. children. map ( ( ) => )?
因为不能保证 props. children将是一个数组。
以下面的代码为例。
html
<Parent>
<h1>有课前端网</h1>
</Parent>
在父组件内部,如果尝试使用 props.children. map映射子对象,则会抛出错误,因为props. children是一个对象,而不是一个数组。
如果有多个子元素, React会使 props.children成为一个数组,如下所示。
html
<Parent>
<h1>有课前端网</h1>
<h2>前端技术学习平台</h2>
</Parent>
不建议使用如下方式,在这个案例中会抛出错误。
class Parent extends Component {
render ( ) {
return (
<div> { this .props.children.map (obj = > obj ) }</div>
)
}
}
建议使用如下方式,避免在上一个案例中抛出错误。
javascript
class Parent extends Component {
render ( ) {
return (
<div> { React.Children.map ( this .props.children, obj => obj) }</div>
)
}
}
12、在使用 React Router时,如何获取当前页面的路由或浏览器中地址栏中的地址?
在当前组件的 props中,包含 location属性对象,包含当前页面路由地址信息,在 match中存储当前路由的参数等数据信息。可以直接通过 this .props使用它们。
react18中可以使用useLocation获取地址相关信息
13、createElement和 cloneElement有什么区别?
- createElement是JSX被转载得到的,在 React中用来创建 React元素(即虚拟DOM)的内容。
- cloneElement用于复制元素并传递新的 props。
14、React- Router有几种形式?
有以下几种形式。
- HashRouter,通过散列实现,路由要带#,不够美观。
- BrowerRouter,利用HTML5中 history API实现,需要服务器端支持,兼容性不是很好。
**15、**React的事件和普通的HTML事件有什么不同?
区别:
- 对于事件名称命名方式,原生事件为全小写,react 事件采用小驼峰;
- 对于事件函数处理语法,原生事件为字符串,react 事件为函数;
- react 事件不能采用 return false 的方式来阻止浏览器的默认行为,而必须要地明确地调用preventDefault()来阻止默认行为。
合成事件是 react 模拟原生 DOM 事件所有能力的一个事件对象,其优点如下:
- 兼容所有浏览器,更好的跨平台;
- 将事件统一存放在一个数组,避免频繁的新增与删除(垃圾回收)。
- 方便 react 统一管理和事务机制。
事件的执行顺序为原生事件先执行,合成事件后执行,合成事件会冒泡绑定到 document 上,所以尽量避免原生事件与合成事件混用,如果原生事件阻止冒泡,可能会导致合成事件不执行,因为需要冒泡到document 上合成事件才会执行。
16、
17、React中D算法的原理是什么?
原理如下。
(1)节点之间的比较。
节点包括两种类型:一种是 React组件,另一种是HTML的DOM。
如果节点类型不同,按以下方式比较:
如果 HTML DOM不同,直接使用新的替换旧的。如果组件类型不同,也直接使用新的替换旧的。
如果 HTML DOM类型相同,按以下方式比较。
在 React里样式并不是一个纯粹的字符串,而是一个对象,这样在样式发生改变时,只需要改变替换变化以后的样式。修改完当前节点之后,递归处理该节点的子节点。
如果组件类型相同,按以下方式比较:
如果组件类型相同,使用 React机制处理。一般使用新的 props替换旧的 props,并在之后调用组件的 componentWillReceiveProps方法,之前组件的 render方法会被调用。
节点的比较机制开始递归作用于它的子节点。
(2)两个列表之间的比较。
一个节点列表中的一个节点发生改变, React无法很妤地处理这个问题。循环新旧两个列表,并找出不同,这是 React唯一的处理方法。
但是,有一个办法可以把这个算法的复杂度降低。那就是在生成一个节点列表时给每个节点上添加一个key。这个key只需要在这一个节点列表中唯一,不需要全局唯一。
(3)取舍
需要注意的是,上面的启发式算法基于两点假设。
类型相近的节点总是生成同样的树,而类型不同的节点也总是生成不同的树
可以为多次 render都表现稳定的节点设置key。
上面的节点之间的比较算法基本上就是基于这两个假设而实现的。要提高 React应用的效率,需要按照这两点假设来开发。
18、概述一下 React中的事件处理逻辑。
为了解决跨浏览器兼容性问题, React会将浏览器原生事件( Browser Native Event)封装为合成事件( Synthetic Event)并传入设置的事件处理程序中。
这里的合成事件提供了与原生事件相同的接口,不过它们屏蔽了底层浏览器的细节差异,保证了行为的一致性。另外, React并没有直接将事件附着到子元素上,而是以单一事件监听器的方式将所有的事件发送到顶层进行处理(基于事件委托原理)。
这样 React在更新DOM时就不需要考虑如何处理附着在DOM上的事件监听器,最终达到优化性能的目的。
19、传入 setstate函数的第二个参数的作用是什么?
第二个参数是一个函数,该函数会在 setState函数调用完成并且组件开始重渲染时调用,可以用该函数来监听渲染是否完成。
javascript
this .setstate ({
username:'有课前端网'
}, ( ) => console.log ( 're-rendered success. ' ) )
20、React和vue.js的相似性和差异性是什么?
相似性如下:
- 都是用于创建UI的 JavaScript库。
- 都是快速和轻量级的代码库(这里指 React核心库)。
- 都有基于组件的架构。
- 都使用虚拟DOM。
- 都可以放在单独的HTML文件中,或者放在 Webpack设置的一个更复杂的模块中。
- 都有独立但常用的路由器和状态管理库。
区别:
- react严格上只能算是MVC的view层,vue则是MVVM模式
- 组件定义方式不同。React使用JSX语法,而Vue可以使用模板或JSX。
- 虚拟DOM实现不同。React使用虚拟DOM进行DOM的渲染和更新,而Vue也使用虚拟DOM,但它更注重数据绑定和响应式系统。
- 数据绑定不同。Vue实现了数据的双向绑定,而React则提倡单向数据流。
- 社区和生态系统不同。React拥有更大的社区和更多的第三方库支持,而Vue也在社区和生态系统方面有着显著的发展。
- 性能不同。在某些情况下,Vue可能提供更快的渲染速度,因为其在虚拟DOM和编译器方面做了一些优化。
- 状态管理方式不同。Vue使用Vuex进行状态管理,而React则使用Redux或Context。
- 设计理念不同。React被设计为可以自底向上逐层应用,而Vue则被设计为可以自顶向下逐层应用
21、生命周期调用方法的顺序是什么?
React生命周期分为三大周期,11个阶段,生命周期方法调用顺序分别如下。
(1)在创建期的五大阶段,调用方法的顺序如下。
getDetaultProps:定义默认属性数据。
getInitialState:初始化默认状态数据。
componentWillMount:组件即将被构建。
render:渲染组件。
componentDidMount:组件构建完成
(2)在存在期的五大阶段,调用方法的顺序如下。
componentWillReceiveProps:组件即将接收新的属性数据。
shouldComponentUpdate:判断组件是否应该更新。
componnentWillUpdate:组件即将更新。
render:渲染组件。
componentDidUpdate:组件更新完成。
(3)在销毁期的一个阶段,调用方法 componentWillUnmount,表示组件即将被销毀。
22、使用状态要注意哪些事情?
要注意以下几点。
不要直接更新状态
状态更新可能是异步的
状态更新要合并。
数据从上向下流动
23、说说 React组件开发中关于作用域的常见问题。
在 EMAScript5语法规范中,关于作用域的常见问题如下。
(1)在map等方法的回调函数中,要绑定作用域this(通过bind方法)。
(2)父组件传递给子组件方法的作用域是父组件实例化对象,无法改变。
(3)组件事件回调函数方法的作用域是组件实例化对象(绑定父组件提供的方法就是父组件实例化对象),无法改变。
在 EMAScript6语法规范中,关于作用域的常见问题如下。
(1)当使用箭头函数作为map等方法的回调函数时,箭头函数的作用域是当前组件的实例化对象(即箭头函数的作用域是定义时的作用域),无须绑定作用域。
(2)事件回调函数要绑定组件作用域。
(3)父组件传递方法要绑定父组件作用域。
总之,在 EMAScript6语法规范中,组件方法的作用域是可以改变的。
24、在 Redux中使用 Action要注意哪些问题?
在Redux中使用 Action的时候, Action文件里尽量保持 Action文件的纯净,传入什么数据就返回什么数据,最好把请求的数据和 Action方法分离开,以保持 Action的纯净。
25、在 Reducer文件里,对于返回的结果,要注意哪些问题?
在 Reducer文件里,对于返回的结果,必须要使用 Object.assign()来复制一份新的 state,否则页面不会跟着数据刷新。
javascript
return Object. assign ( { }, state, {
type:action .type,
shouldNotPaint : true
})
26、React中的setState是同步执行还是异步执行?如果是异步的?怎么拿到执行后的state?
setState是异步的。
如果要拿到修改后的状态,需要使用回调函数的方式,如下:
javascript
//改变状态后想做一些事情:
this.setState({
属性名:属性值
}, () => {
//一般是用于在setState之后做一些操作
//this.state == 修改之后的state
})
为什么不能直接用以下办法更新state
javascript
this.state.msg = "hello";
因为,这样不会引起组件的重新渲染,所以,数据修改后没法 呈现在页面上。
而调用setState()函数,会引起组件的重新渲染,这样更新的数据就会呈现在页面上
27、
**28、**Redux内部原理 内部怎么实现dispstch一个函数的
以
redux-thunk
中间件作为例子,下面就是thunkMiddleware
函数的代码
javascript
// 部分转为ES5代码,运行middleware函数会返回一个新的函数,如下:
return ({ dispatch, getState }) => {
// next实际就是传入的dispatch
return function (next) {
return function (action) {
// redux-thunk核心
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
};
}
redux-thunk
库内部源码非常的简单,允许action
是一个函数,同时支持参数传递,否则调用方法不变
redux
创建Store
:通过combineReducers
函数合并reducer
函数,返回一个新的函数combination
(这个函数负责循环遍历运行reducer
函数,返回全部state
)。将这个新函数作为参数传入createStore
函数,函数内部通过dispatch,初始化运行传入的combination
,state生成,返回store对象redux
中间件:applyMiddleware
函数中间件的主要目的就是修改dispatch
函数,返回经过中间件处理的新的dispatch
函数redux
使用:实际就是再次调用循环遍历调用reducer
函数,更新state
29、如果创建了类似于下面的 Icketang元素,那么该如何实现 Icketang类?
html
< Icketang username="雨夜清荷">
{user = > user ?<Info user={user} />:<Loading />}
</Icketang>
import React, { Component } fromr "react";
export class Icketang extends Component {
//请实现你的代码
}
在上面的案例中,一个组件接受一个函数作为它的子组件。Icketang组件的子组件是一个函数,而不是一个常用的组件。这意味着在实现 Icketang组件时,需要将props. children作为一个函数来处理。
具体实现如下。
javascript
import React, { Component } from "react";
class Icketang extends Component {
constructor ( props ){
super ( props )
this .state = {
user : props.user
}
}
componentDidMount( ) {
//模拟异步获取数据操作,更新状态
setTimeout ( ( ) => this .setstate ({
user:'有课前端网'
}),2000)
}
render ( ) {
return this.props.children ( this .state.user )
}
}
class Loading extends Component {
render ( ) {
return <p>Loading.</p>
}
}
class Info extends Component {
render ( ) {
return <hl> { this .props.user }</h1>
}
}
调用 Icketang组件,并传递给user属性数据,把 props.children作为一个函数来处理。这种模式的好处是,我们已经将父组件与子组件分离了,父组件管理状态。父组件的使用者可以决定父组件以何种形式渲染子组件。
为了演示这一点,在渲染 Icketang组件时,分别传递和不传递user属性数据来观察渲染结果。
javascript
import {render} from "react-dom";
render (<Icketang>
{ user = > user ?<Info user = {user} /> :<Loading /> }
</Icketang> , ickt)
上述代码没有为 Icketang组件传递user属性数据,因此将首先渲染 Loading组件,当父组件的user状态数据发生改变时,我们发现Info组件可以成功地渲染出来。
javascript
render(< Icketang user="雨夜清荷">
{ user => user ?<Info user = {user} /> :<Loading />}
</Icketang>, ickt)
上述代码为 Icketang组件传递了user属性数据,因此将直接渲染Info组件,当父组件的user状态数据发生改变时,我们发现Info组件产生了更新,在整个过程中, Loading组件都未渲染。
30、这段代码有什么问题?
javascript
class App extends Component {
constructor ( props ) {
super ( props )
this .state = {
username:"有课前端网",
msg:' '
}
}
render ( ) {
return (
<div> { this .state. msg }</div>
);
}
componentDidMount ( ) {
this .setState ( ( oldState, props ) => {
return {
msg:oldState .username + ' - ' + props.intro
}
} )
}
render ( < App intro=" 前端技术专业学习平台"></App>,ickt )
在页面中正常输出"有课前端网-前端技术专业学习平台"。但是这种写法很少使用,并不是常用的写法。React允许对 setState方法传递一个函数,它接收到先前的状态和属性数据并返回一个需要修改的状态对象,正如我们在上面所做的那样。它不但没有问题,而且如果根据以前的状态( state)以及属性来修改当前状态,推荐使用这种写法。
31、请说岀 React从 EMAScript5编程规范到 EMAScript6编程规范过程中的几点改变。
主要改变如下。
(1)创建组件的方法不同。
EMAScript5版本中,定义组件用 React.createClass。EMAScript6版本中,定义组件要定义组件类,并继承 Component类。
(2)定义默认属性的方法不同。
EMAScript5版本中,用 getDefaultProps定义默认属性。EMAScript6版本中,为组件定义 defaultProps静态属性,来定义默认属性。
(3)定义初始化状态的方法不同。
EMAScript5版本中,用 getInitialState定义初始化状态。EMAScript6版本中,在构造函数中,通过this. state定义初始化状态。
注意:构造函数的第一个参数是属性数据,一定要用 super继承。
(4)定义属性约束的方法不同。
EMAScript5版本中,用 propTypes定义属性的约束。
EMAScript6版本中,为组件定义 propsTypes静态属性,来对属性进行约束。
(5)使用混合对象、混合类的方法不同。
EMAScript5版本中,通过mixins继承混合对象的方法。
EMAScript6版本中,定义混合类,让混合类继承 Component类,然后让组件类继承混合类,实现对混合类方法的继承。
(6)绑定事件的方法不同。
EMAScript5版本中,绑定的事件回调函数作用域是组件实例化对象。
EMAScript6版本中,绑定的事件回调函数作用域是null。
(7)父组件传递方法的作用域不同。
EMAScript5版本中,作用域是父组件。 EMAScript6版本中,变成了null。
(8)组件方法作用域的修改方法不同。
EMAScript5版本中,无法改变作用域。
EMAScript6版本中,作用域是可以改变的。