React面试题(一)
1. 什么是React
React,用于构建用户界面的Javascript库,只提供了UI层面的解决方案
遵循组件设计模式,声明式编程规范和函数式编程概念,以使前端应用更高效使用虚拟DOM来有效地运作DOM,遵循从高阶组件到低阶组件的单向数据流帮助我们将界面变成了各个独立的小块,每一个就是一个组件,这些组件之间可以组合,嵌套,构成整体页面
react类组件使用一个名为render()的方法或者函数组件return,接收输入的数据并返回需要展示的内容
class HelloMessage extends React.Component{
render(){
return <div>Hello {this.props.name} </div>;
}
}
ReactDOM.render(
<HelloMessage name="Taylor" />,
document.getElementById("hello-example")
);
上述这种类似 XML形式就是JSX,最终会被Babel编译为合法的 JS语句调用
特征
React 特征很多,如:
- JSX语法
- 单项数据绑定
- 虚拟DOM
- 声明式编程
- Component
着重介绍下声明式编程及Component
声明式编程
声明式编程是一种编程范式,它关注的是你要做什么,而不是如何做它表达逻辑
而不显示定义步骤。这意味这我们需要根据逻辑的计算来声明要显示的组件如实现
一个标记的地图:
通过命令式创建地图,创建标记,以及在地图上添加的标记的步骤如下:
// 创建地图
const map = new Map.map(document.getElementById("map"),{
zoom:4,
center:{lat,lng},
});
// 创建标记
const marker = new Map.marker({
position:{ lat,lng }
title:"Hello Marker",
})
// 地图上添加标记
marker.setMap(map);
而用React实现上述功能则如下:
<Map zoom={4} center={{lat,lng}}>
<Marker position={{lat,lng}} title={{"Hello Marker"}} />
</Map>
声明式编程是的 React 组件很容易使用,最终的代码简单易于维护
Component
在 React 中,一切皆为组件。通常将应用程序的整个逻辑分解为小的单个部分,我们将每个单独的部分为组件
组件可以是一个函数或者是一个类,接受数据传入,处理它并返回在UI中呈现的 React 元素,函数式组件如下:
const Header = () => {
return (
<Junbotron style={{ backgroundColor: "orange" }}>
<h1>Test</h1>
</Junbotron>
);
};
类组件(有状态组件)如下:
class Dashboard extends React.Component {
constructor(props){
super(props);
this.state = {};
}
render(){
return (
<div className="dashboard">
<ToDoForm />
<ToDolist />
</div>
);
}
}
一个组件该有的特点如下:
- 可组合:每个组件易于和他组件一起使用,或者嵌套在另一个组件内部
- 可重用:每个组件都是具有独立功能的,它可以被使用在多个UI场景
可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护
优势
通过上面的初步了解,可以感受到React存在的优势:
- 高效灵活
- 声明式的设计,简单易用
- 组件式开发,提高代码复用率
单项响应的数据流会比双向绑定的数据流更安全,速度更快
2.state和props有什么区别?
state
一个组件的显示形态可以由数据状态和外部参数所决定,而数据状态就行state,一般在constructor中初始化
当需要修改里面的值的状态需要通过setState来改变,从而达到更新组件内部数据的作用,并且重新调用组件render方法,如下面的例子:
class Button extends React.Component {
constructor(){
super();
this.state = {
count:0,
};
}
updateCount(){
this.setState((prevState,props)=>{
return {count:prevState.count + 1};
});
}
render(){
return (
<button onClick={()=>this.updateCount()}>
Clicked {this.state.count} times
</button>
);
}
}
setState 还可以接受第二个参数,他是一个函数,会在setState调用完成并且组件开始重新渲染时被调用,可以用来监听渲染是否完成
this.setState(
{
name:"JS每日一题",
},
()=>console.log("setState finished")
);
props
React 的核心思想就是组件化思想,页面会被切分成一些独立的,可复用的组件
组件从概念上就是一个函数,可以接受一个参数作为输入值,这个参数就是porps,所以可以把props理解为从外部传入组件内部的数据
react具有单项数据流的特性,所以它的主要作用是从父组件向子组件中传递数据
props除了可以传递字符串,数字,还可以传递对象,甚至是回调函数,如下:
class Welcome extends React.Component{
render(){
return <h1>Hello {this.props.name}</h1>;
}
}
const element = <Welcome name="Sara" onNameChanged={this.handleName} />;
上述name属性与onNameChange方法都能在子组件的props变量中访问
在子组件中,props在内容不可变的,如果想要改变他看,只能通过外部组件传入新的props来重新渲染子组件,否则子组件的props和展示形式不会改变
区别
相同点:
- 两者都是Javascript对象
- 两者都是用于保存信息
- props和state都能触发渲染更新
区别:
- props是外部传递给组件的,而state是在组件内部自己管理的,一般在constructor中初始化
- props在组件内部是不可修改的,但state在组件内部可以修改
- state是多变的,可以修改
3.super() 和 super(props)有什么区别?
ES6类
在ES6中,通过extends关键字实现类的继承,方式如下:
class sup{
constructor(name){
this.name = name;
}
printName(){
console.log(this.name)
}
}
class sub extends sup{
constructor(name,age){
super(name); // super代表的是父类的构造函数
this.age = age;
}
printAge(){
console.log(this.age);
}
}
let jack = new sub("jack",20);
jack.printName(); // 输出:Jack
jack.printAge(); // 输出:20
在上面的例子中,可以看到通过supper关键字实现调用父类,super代替的是父类的构造函数,使用super(name) 相当于调用 sup.prototype.contructor.call(this,name)
如果在子类中不使用supper,关键字,则会引发报错。报错的原因是 子类没有自己的this对象的,它只能继承父类的this对象,然后对其进行加工而super()就是将父类中的this对象继承给子类的,没有super()子类就得不到this对象
如果先调用this,再初始化super(),同样是静止的行为
class sub extends sup {
constructor(name,age){
this.age = age;
super(name); // super代表的是父类的构造函数
}
}
所以在子类construct中,必须先调用super才能引入this
类组件
在React中,类组件是基于ES6的规范实现的,继承React.Component,因此如果用到constructor就必须写super()才能初始化this
这时候,在调用super()的时候,我们一般都需要传入props作为参数,如果不传进去,React内部也会将其定义在组件实例中
// React内部
const instance = new YourComponent(props);
instance.props = props;
所以无论有没有construct,在render中this.props都是可以使用的,这是React自动附带的,是可以不写的:
class HelloMessage extends React.Component{
render(){
return <div>nice to meet you! {this.porps.name}</div>
}
}
但是也不建议使用super() 替代 super(props)
因为在React会在类组件构造函数生成实例后再给this.props赋值,所以在不传递props在super的情况下,调用this.props为undefind,如下情况:
class Button extends React.Component{
constructor(props){
super(); // 没传入 props
console.log(props); //{}
console.log(this.props); // undefined
// ...
}
}
而传入props的则能正常访问,确保了this.props在构造函数执行完毕之前已经被复制,
class Button extends React.Component{
constructor(pros){
super(props); // 没有传入props
console.log(props); // {}
console.log(this.props); // {}
// ...
}
}
总结
在React中,类组件基于ES6,所以在constructor中必须使用super在调用super过程中,无论是否传入props,React内部都会将porps赋值给组件实例porps
如果只调用了super(),那么this.props在supper()和构造函数结束之间仍是undefind
说说对React中类组件和函数组件的理解?区别
类组件
类组件,顾名思义,也就是通过使用ES6类的编写形式去编写组件,该类必须继承React.Component如果想要访问父组件传递过来的参数,可通过this.props的方式去访问
在组件中必须实现render方法,在return中返回React对象,如下:
class Welcome extends React.Componet{
constructor(props){
super(props)
}
render(){
return <h1>Hello,{this.props.name}</h1>
}
}
函数组件
函数组件,顾名思义,就是通过函数编写的形式去实现一个React组件,是React中定义组件最简单的方式
function Welcome(props){
return <h1>Hello,{props.name}</h1>
}
函数第一个参数为props用于接收父组件传递过来的参数
区别
针对两种React组件,其区别主要分成一下几大方向:
- 编写形式
- 状态管理
- 生命周期
- 调用方式
- 获取渲染的值
编写形式
两者最明显的区别在于编写形式的不同,同一种功能的视线可以分别对应组件和函数组件的编写形式函数组件:
function Welcome(props){
return <h1>Hello,{props.name}</h1>
}
类组件:
class Welcome extends React.Component{
constructor(){
super(props)
}
render(){
return <h1>Hello,{this.props.name}</h1>
}
}
状态管理
在hooks出来之前,函数组件就是无状态组件,不能保管组件的状态,不想类组件中调用setState
如果想要state状态,可以使用useState,如下:
const FunctionalComponent = ()=> {
const [count,setCount] = React.useState(0);
return (
<div>
<p>count:{count}</p>
<button onClick={()=>setCount(count+1)>Click</button>
</div>
);
};
在使用hooks情况下,一半如果函数组件调用State,则需要创建一个类组件或者state提升到你的父组件中,然后通过props对象传递到子组件
生命周期
在函数组件中,并不存在生命周期,这是因为这些生命周期勾子都是来自继承的React.Component所以,如果用到生命周期,就只能使用类组件
但是函数组件使用useEffect也能够完成替代生命周期的作用,这里给出一个例子:
const FunctionalComponent = () => {
useEffect(()=>{
console.log("hello");
},[]);
return <h1>Hello,World</h1>
}
上述例子对应类组件中的componentDidMount生命周期
如果在useEffect回调函数中return一个函数,则return函数会在组件卸载的时候执行,正如componentWillUnmount
const FunctionalComponet = () => {
React.useEffect(()=>{
return () => {
console.log("Bye")
}
},[]);
return <h1>Bye,World</h1>
}
调用方式
如果是一个函数组件,调用则是执行函数即可:
// 你的代码
function SayHi(){
return <p>hello,React</p>
}
// React内部
const result = SayHi(props) // <p>Hello,React</p>
如果是一个类组件,则需要将组件进行实例化,然后调用实例对象的render方法:
class SayHi extends React.Component {
render(){
return <p>Hello,React</p>
}
}
// React内部
const instance = new SayHi(props) // SayHi{}
const result = instance.render() // <p>Hello,React</p>
获取渲染的值
首先给出一个示例
函数组件对应如下:
function ProfilePage(props){
const showMessage = () =>{
alert('Followed ' + props.user);
}
const handleClick = () =>{
setTimeout(showMessage,3000);
}
return (
<button onClick={bandleClick}>Follow</button>
)
}
类组件对应如下:
class ProfilePage extends React.Component {
showMessage(){
alert('Followed' + this.props.user);
}
handleClick(){
setTimeout(this.showMessage.bind(this),3000);
}
render(){
return <button onClick={this.handleClick.bind(this)}>Follow</button>
}
}
两者看起来实现的功能是一致的,但是在类组件中,输出this.props.user,Props在React中是不可变的所以他永远不会改变,但是this总量是可变的,以便您可以在render和生命周期函数中读取新版本
因此,如果我们的组件在请求运行时更新,this.props将会改变。showMessage方法从“最新”的props中读取user
而函数组件,本身就不存在this,props并不发生改变,因此同样是点击,alert的内容仍然是之前的内容
小结
两种组件都有各自的优缺点
函数组件语法更短,更简单,这使得他更容易开发,理解和测试
而类组件也会因大量使用this而让人感到困惑
说说对受控组件和非受控组件的理解?应用场景?
受控组件
受控组件,简单来讲,就是受我们控制的组件,组件的状态全程响应外部数据
举个简单例子:
class TestCompontnt extends React.Component{
constructor(props){
super(props);
this.state = {username:'lindaidai'};
}
render(){
return <input name="username" value={this.state.username} />
}
}
这时候当我们在输入框输入内容的时候,会发现输入的内容并无法显示出来,也就是input标签是一个可读的状态
这是因为value被this.state.username所控制住,当用户输入新的内容时,this.state.username并不会自动更新,这样的话input内的内容也就不会变了
如果想要被控制,可以为input标签设置onChange事件,输入的时候触发事件函数,在函数内实现state的更新,从而导致input框的内容页发生改变
因此,手控组件我们一般要初始状态和一个状态更新事件函数
非受控组件
非受控组件,简单来讲,就是不受我们控制的组件
一般情况是在初始化的时候接受外部数据,然后自己在内部存储其自身状态
当需要时,可以用ref查询DOM并查找其当前值,如下:
import React,{ Component } from 'react';
export class UnControll extends Component{
constructor(props){
super(props);
this.inputRef = React.createRef();
}
handleSubmit = (e) =>{
console.log("我们可以获得的input内的值为",this.inputRef.current.value);
e.preventDefault();
}
render(){
return (
<form onSubmit={e => this.handleSubmit(e) >
<input defaultValue="lindaidai" ref={this.inputRef} />
<input type="submit" value="提交" />
</form>
)
}
}
应用场景
大部分时候推荐使用受控组件来实现表单,因为在受控组件中,表单数据由React组件负责处理如果选择非受控组件的话,控制能力较弱,表单数据就由DOM本身处理,但更加方便快捷,代码量少针对两者的区别,其应用场景如下图所示:
特征 | 不受控制 | 受控制 |
---|---|---|
一次性取值(例如,提交时) | √ | √ |
提交时验证 | √ | √ |
即时现场验证 | × | √ |
有条件地禁用提交按钮 | × | √ |
强制输入格式 | × | √ |
一个数据的多个输入 | × | √ |
动态输入 | × | √ |
感谢大家观看,我们下次见