适用人群:刚开始学习 react 框架的同学,当作参考查阅。
说明:多以自己分析整理为主,如有错误理解请指正,谢谢!
笔记来源
开始 – React (Q1-2,有点傻的问题已经删掉了)
尚硅谷React教程(2022加更,B站超火react教程)_哔哩哔哩_bilibili (Q3-)
Q3:jsx语法规则详细
A3:如下
定义虚拟dom时,不要写引号
标签中混入js表达式要用
{}
样式的类名指定不要用class,用className
内联样式,要用
style={{key:value}}
的形式去写只有一个根标签
标签必须闭合
标签首字母
- 若小写字母开头,则将该标签转为html中的同名元素,若html中无该标签对应的同名元素。则报错
- 若大写字母开头,react就去渲染对应的组件,若组件未定义,则报错
Q4:react创建虚拟dom的两种方式
A4:jsx和React.createElement()
Q5:真实dom与虚拟dom是什么与其的区别
是什么?virtual dom本质是js对象形式对dom的描述,而页面渲染出的每一个节点则是一个Real dom。
区别1:虚拟dom不会进行排版和重绘操作,而真实dom会频繁重排与重绘。
区别2:虚拟dom的总消耗是“虚拟dom增删改 + 真实dom差异增删改 + 排版与重绘”,真实dom的总消耗是“真实dom的完全增删改 + 排版与重绘”
本题题解:https://github.com/febobo/web-interview/issues/181
Q6:react类组件中this的指向问题?构造函数/render方法/自定义方法
构造函数一定指向类实例本身
render方法是react new出实例后调用,因此this为类实例本身
类中自定义方法this指向,如下changeWeather中this为undefined
//A6案例 此时自定义函数中this指向存在问题
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
}
render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}
changeWeather(){
//changeWeather放在了哪里? -- Weather原型对象上,供所有实例使用
//通过Weather实例调用changeWeather时,changeWeather中的this就是Weather实例
//但由于一般自定义方法都是绑定在onClick上,因此不是实例调用,只是在绑定时将这个函数给了onClick做回调而已。当用户点击时是直接去堆内存中找到这个函数然后直接调用,因此this在严格模式下一般都为undefined
console.log(this)
}
}
Q7:react类组件自定义的方法中的this为undefined,如何解决?两种解决方案如下
//A7,1(第一种解决方案:强制绑定this:通过函数对象的bind())
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
//实例对象自身多了一个changeWeather,后续调用即可
this.changeWeather = this.changeWeather.bind(this)
}
render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}
changeWeather(){
//changeWeather放在了哪里? -- Weather原型对象上,供所有实例使用
//通过Weather实例调用changeWeather时,changeWeather中的this就是Weather实例
//但由于一般自定义方法都是绑定在onClick上,因此不是实例调用,只是在绑定时将这个函数给了onClick做回调而已。当用户点击时是直接去堆内存中找到这个函数然后直接调用,因此this在严格模式下一般都为undefined
console.log(this)
}
}
//A7,2(第二种解决方案:使用赋值语句和箭头函数书写changeWeather
class Weather extends React.Component{
constructor(props){
super(props)
this.state = {isHot :false}
}
render(){
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}
changeWeather = () => {
console.log(this)
}
}
Q8:什么是state? 如何在类组件中使用state?
A8,1什么是state?
state是组件对象最重要的属性,值是对象(可以包含多个key-value的组合)
组件被称为“状态机”,通过更新组件的state来更新对应的页面显示(重新渲染组件)
//A8,2如何在类组件中使用state?
//this.state.xxx = this.state.xxx + 1 //错误写法(状态数据,不能直接修改或更新)
this.setState({xxx:!xxx})//正确写法(使用react内置api去更改),注意此api的使用是合并操作
Q9:精简类组件?干掉constructor
//A9
class Weather extends React.Component{
//内部状态存在于Weather实例对象
state = {isHot :false}
render() {
return <h1 onClick={this.changeWeather}>今天天气很{this.state ? "炎热" : "凉爽"}</h1>
}
//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
Q10
什么是props?
在类组件中props的使用?
对props的一些限制?
props简写方式?
类构造函数中props的使用?
函数组件中props的使用?
A10,1 什么是props?
每个组件对象都有props(properties的简写)属性
组件标签所有属性都保存在props中
//A10,2在类组件中props的使用?
class Person extends React.Component{
//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
//props传参方法1,要求键值对的形式,如果要传数值,要用{}表示是js中的数值
ReactDOM.render(<Person name="whh" age={18} sex="男"/>,document.querySelector(".test"))
//props传参方法2
const p1 = {name:"whs",age:18,sex:"男"}
ReactDOM.render(<Person {...p}/>,document.querySelector(".test1"))
//A10,3对props的一些限制与指定默认值?需要引入 prop-type.js 对标签属性类型以及必要性的限制
//目前是给Person类自身加属性,在外侧
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
Person.defaultProps = {
sex:"男"
age:18
}
//A10,4props简写方式?在类中给Person类自身加属性
class Person extends React.Component{
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
static defaultProps = {
sex:"男"
age:18
}
//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
//A10,5构造函数中props的使用?接住props并传给super与不接住不传的区别
class Person extends React.Component{
constructor(props){
//接住props并传给super后在可以访问构造函数中this.props,但不传给super则constructor中this.props为undefined,可以分别打开以下两行代码试试看
//super(props)
//super()
console.log(this.props)
}
static propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
static defaultProps = {
sex:"男"
age:18
}
//内部状态存在于Weather实例对象
render() {
const {name,sex,age} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//自定义方法 - 要用赋值语句的形式 + 箭头函数
changeWeather = () => {
console.log(this)
}
}
//A10,6函数组件中props的使用?
function Person(props){
const {name,sex,age} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
//对标签属性类型以及必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired,
sex:PropTypes.string,
age:PropTypes.number
speak:PropTypes.func//方法限制要用func而不是function关键字
}
//指定默认标签属性值
Person.defaultProps = {
sex:"男"
age:18
}
//props传参方法1,要求键值对的形式,如果要传数值,要用{}表示是js中的数值
ReactDOM.render(<Person name="whh" age={18} sex="男"/>,document.querySelector(".test"))
Q11:ref的三种创建方法
string绑定在标签
回调Refs (内联与类的绑定函数)
React.createRef()
//string绑定在标签
class Demo extends React.Component {
showData = () => {
console.log(this.refs.input);
};
render(): React.ReactNode {
return (
<div>
<input ref="input" type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//回调Refs 内联回调
class Demo extends React.Component {
showData = () => {
const {input} = this
console.log(input)
};
render(): React.ReactNode {
return (
<div>
<input ref={(currectNode) => {this.input = currectNode}} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//回调Refs 类的绑定函数
class Demo extends React.Component {
showData = () => {
const {input} = this
console.log(input)
};
savaInput = (c) => {
this.input = c
console.log(c)
}
render(): React.ReactNode {
return (
<div>
<input ref={this.savaInput} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
//React.createRef()
class Demo extends React.Component {
myRef = React.creatRef()
showData = () => {
const {input} = this
console.log(input)
};
savaInput = (c) => {
this.input = c
console.log(c)
}
render(): React.ReactNode {
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"></input>
<button onClick={this.showData}>点我提示左侧数据</button>
</div>
);
}
}
Q12:非受控组件与受控组件的理解?
非受控组件,一般是用ref实现,当用户提交表单时再去获取内容(现用现取节点值,不受我们监听state状态)
受控组件,一般用onChange来实现的,当用户改变input内容的时候自动调用onChange的回调然后去改变state中的状态(类似于vue中的双向绑定)
//非受控组件案例 ,现用现取
class Login extends React.Component {
handleSubmit = (event) => {
const { username, password } = this;
if (username != null && password != null) {
alert(
`你输入的用户名是:${username.value}},你输入的密码是:${password.value}`
);
}
};
render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input ref={(c) => (this.username = c)} type="text" name="username" />
<input
ref={(c) => (this.password = c)}
type="password"
name="password"
/>
<button>登录</button>
</form>
);
}
}
//onChange与state是受控组件实现的重点
class Register extends React.Component {
state = { username: "", password: "" };
saveUsername = (event: any) => {
this.setState({ username: event.value });
};
savePassword = (event: any) => {
this.setState({ username: event.value });
};
handleSubmit = (event: any) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveUsername} type="text" name="username" />
<input onChange={this.savePassword} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
Q13:什么是高阶函数与函数柯里化?在react中的使用场景?
高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
若A函数,接收的参数是一个函数,那么A就可以称为高阶函数
若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
常用的高阶函数有:Promise、setTimeout、数组的方法
函数柯里化:通过调用 继续返回函数 的方式,实现多次接受参数最后统一处理的函数编码方式
//A13:函数柯里化,原生js案例
//未使用函数柯里化的求和函数
/* function sum(a,b,c){
return a+b+c
} */
function sum (a) {
return (b) => {
return (c) => {
return a+b+c
}
}
}
const result = sum(1)(2)(3)
//A13 函数柯里化在react中的使用
class Register extends React.Component {
state = { username: "", password: "" };
saveUsername = (event: any) => {
this.setState({ username: event.value });
};
savePassword = (event: any) => {
this.setState({ username: event.value });
};
saveData = (dataType:string) => {
return (event:any) => {
this.setState([dataType],event.target.value)
}
}
handleSubmit = (event: any) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={this.saveData("username")} type="text" name="username" />
<input onChange={this.saveData("password")} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
Q14:不用高阶函数和函数柯里化也能接受到监听事件中传入的dataType和event(如下列代码所示)
//A13 函数柯里化在react中的使用
class Register extends React.Component {
state = { username: "", password: "" };
saveUsername = (event) => {
this.setState({ username: event.value });
};
savePassword = (event) => {
this.setState({ username: event.value });
};
saveData = (dataType,event) => {
this.setState([dataType],event.target.value)
}
handleSubmit = (event) => {
const { username, password } = this.state;
alert(`你输入的用户名是:${username},你输入的密码是:${password}`);
};
render(): React.ReactNode {
return (
<form onSubmit={this.handleSubmit}>
<input onChange={(event) => {this.saveData("username",event)}} type="text" name="username" />
<input onChange={(event) => {this.saveData("password",event)}} type="password" name="password" />
<button>登录</button>
</form>
);
}
}
Q15:旧生命周期钩子流程介绍?(React16.x以及之前)
初始化阶段:由ReactDOM.render()触发 --初次渲染
- constructor()
- componentWillMount()
- render()
- componentDidMount() -->常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)
更新阶段:由组件内部this.setState()或父组件render触发
- shouldComponentUpdate()
- componentWillUpdate()
- render() -->必须调用一个
卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount() -->常用 (一般在这个钩子中做一些收尾的工作,例如:关闭定时器、取消订阅消息)
Q16:新生命钩子的流程介绍?(React17.x-至今)
初始化阶段:由ReactDOM.render()触发 --初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount() -->常用 (一般在这个钩子中做一些初始化的工作,例如:开启定时器、发送网络请求、订阅消息)
更新阶段:由组件内部this.setState()或父组件render触发
- getDerivedStateFromProps
- shouldComponentUpdate()
- render() -->必须调用一个
- getSnapshotBeforeUpdate
- componentDidUpdate()
卸载组件:由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
Q17
react/vue中的key有什么作用?(key的内部原理是什么)
为什么遍历列表时,key最好不要用index?
虚拟DOM中key的作用:
- 简单的说:key是虚拟DOM对象的表示,在更新显示时key起着至关重要的作用
- 详细的说:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】和【旧虚拟DOM】的diff比较,比较规则如下:
- 旧虚拟DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没有变,则直接用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,替换掉页面之前的真实DOM
- 旧虚拟DOM未找到与新虚拟DOM相同的key,根据数据创建新的真实DOM,随后渲染到页面
用index作为key可能会引发的问题:
- 若对数据进行:逆向添加,删除等破坏规则操作:会产生没有必要的真实DOM的更新 --> 虽然界面没问题,但效率低
- 如果结构中还包含输入类的DOM:会产生错误DOM更新 --> 界面有问题
- 注意!如果不存在对数据的逆向添加、逆向删除等破坏顺序操作。仅用了渲染列表用于展示,使用index作为key是没有问题的
Q18:todoList案例相关知识点
- 拆分组件、实现静态组件,注意className、style的写法
- 动态初始化列表、如何确定将数据放在哪个组件的state中?
- 某个组件使用:放在其自身的state中
- 某些组件使用:放在他们的共同的父组件state中(官方称为:状态提升)
- 关于父子之间通信:
- 【父组件】给【子组件】传递数据:通过props传递
- 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
- 注意 defaultChecked 和 checked 的区别,类似还有:defaultValue 和 value
- 状态在哪里,操作状态的方法就在哪里
Q19:github搜索案例涉及知识点
设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办
ES6小知识点:解构赋值 + 重命名
let obj = {a:{b:1}}
const {a} = obj
const {a:{b}} = obj
const {a:{b:value}} = obj
- 消息订阅与发布机制
- 先订阅,再发布
- 适用于任何组件通信
- 要在组件中componentWillUnmount中取消订阅
- fetch发送请求(关注分离的设计思想)
Q20:前端路由基本原理
如果想在原生中自己封装一个路由库可以基于history封装好的基石(去bootcdn上搜索history)
方法一:直接使用h5推出的history身上的api
方法二:hash值(瞄点)路径带#(兼容性强)
Q21:react路由的基本使用
温馨提示:目前尚硅谷教的是5版本 npm i react-router-dom@5
1.明确好界面导航区和展示区
2.导航区的a标签改为Link标签
3.展示区些Route标签进行路径的匹配
<Route path='/xxx' component={Demo}/>
4.<App>
的最外侧包裹了一个<BrowserRouter>
或者<HashRouter>
Q22:路由组件与一般组件
写法不同
- 一般组件:
<Demo />
- 路由组件:
<Route path="/demo" component={Demo} />
- 一般组件:
存放位置不同
- 一般组件:component
- 路由组件:pages
接收的props不同
- 一般组件:写组件标签时传递了什么,就能收到什么
- 路由组件:接受3个固定的属性
history: go: f go(n) goBack:f goBack() goForward:f goForward() push:f push(path,state) replace:f replace(path,sstate) Location: pathname:"/about" search:"" state:undefined match: params:{} path:"/about" url:"/about"
Q23:NavLink与封装NavLink
NavLink可以实现路由链接的高亮,通过activeClassName指定样式名
标签内容是一个特殊属性(children)
通过this.props.children可以获取标签体中的内容
Q24: react解决样式丢失问题
- 在public下的html中解决
<!--不要使用的方式-->
<link href="./css/bootstrap.css" rel="stylesheet">
<!--方案一:在本html中直接往css中查找,而不会带上前端路由-->
<link href="/css/bootstrap.css" rel="stylesheet">
<!--方案二:在public相对路径下查找,%PUBLIC_URL%只有在react中有用-->
<link href="%PUBLIC_URL%/css/bootstrap.css" rel="stylesheet">
- 使用router的HashRouter解决,因为#后的网络访问时认为是前端资源,因此不会将前端路由带上。
Q25:react路由的严格匹配和模糊匹配
默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)
开启严格匹配
<Route exact={true} path="/about" component={About}/>
- 严格模式不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
Q26:路由重定向的使用
<Switch>
<Route path="/about" component={About} />
<Route path="/home" component={Home} />
<!--重定向兜底使用,上面的都匹配不到就去找to的路径-->
<Redirect to="/about" />
</Switch>
Q27:嵌套路由
注册子路由时要写上父路由的path值
路由的匹配是按照注册路由的顺序进行的
Q28:向路由组件传递参数(使用优先级从上到下)
- params参数
<!--路由链接(携带参数)-->
<Link to="/demo/test/tom/18">详细</Link>
<!--注册路由(声明接收)-->
<Route path="/demo/test/:name/:age" component={Test} />
<!--接收参数:this.props.match.params-->
- search参数
<!--路由链接(携带参数)-->
<Link to="/demo/test?name='tom'&age=18">详细</Link>
<!--注册路由(无需声明,正常注册即可)-->
<Route path="/demo/test" component={Test} />
<!--接收参数:this.props.location.search-->
<!--备注:获取到的search是urlencoded编码字符串,需要借助querystring解析-->
3 state参数(需要处理保密数据用)
<!--路由链接(携带参数)-->
<Link to={{path:'/demo/test',state:{name:'tom',age:18}}}>详细</Link>
<!--注册路由(无需声明,正常注册即可)-->
<Route path="/demo/test" component={Test} />
<!--接收参数:this.props.location.state-->
<!--备注:刷新也可以正常保留参数-->
Q29:编程式路由导航
借助this.props.history对象上的api对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.histroy.goForward()
this.props.history.go()
Q30:withRouter的使用
withRouter可以加工一般组件,让一般组件具备由组件所特有的api
withRouter的返回值是一个新组件
Q31:BrowserRouter和HashRouter的区别
底层原理不一样
- BrowserRouter使用的是H5的history api,不兼容ie9及以下版本
- HashRouter使用的是url的哈希值
url表现形式不一样
- BrowserRouter的路径中没有#,例如:localhost:3000/demo/test
- HashRouter的路径中包含#,例如localhost:3000/#/demo/test
刷新后对路由state参数的影响
- BrowserRouter没有任何影响,因为state保存在history对象中
- HashRouter刷新后会导致由state参数的丢失
备注:HashRouter可以用于解决一些路径错误相关的问题
Q32:UI组件库
国内使用多的antd
官方文档:https://ant.design/docs/react/introduce-cn
主要掌握:按需引入和自定义主题
国外使用多material ui
官方文档:https://material.io/
Q33:求和案例_redux精简版
去除Count组件自身状态
src下建立:
- -redux
- store.js
- count_reducer.js
- -redux
store.js
- 引入redux的createStore函数,创建一个store
- createStore调用时要传入一个为其服务的reducer
- 暴露store给你的组件使用
count_reducer.js
- reducer的本质是一个函数,接收:preState,action,返回加工后的状态
- reducer有两个作用:初始化状态,加工状态
- reducer被第一次调用时,是store自动触发的,传递的preState是undefined
在index.js中检测store中状态的改变,一旦发生改变重新渲染
- 备注:redux只负责管理状态,至于状态的改变驱动着页面的展示,要靠我们自己写
Q34:求和案例_redux异步action版
明确:延迟的动作不想交给组件自身,想交给action
何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回。
具体编码:
- npm i redux-thunk,并配置在store中
- 创建action的函数不再返回一个一般对象,而是一个正在的函数,该函数中写异步任务
- 异步任务有结果后,分发一个同步的action去真正操作数据
备注:异步action不是必须要写的,完全可以自己等待异步任务的结果了再去分发同步的action
Q35:react-redux
使用目的是讲react组件和redux api分开
明确两个概念:
- UI组件:不能使用任何的redux的api,只负责页面的呈现和交互等
- 容器组件:负责和redux通信,将结果交给UI组件
如何创建一个容器组件 - 靠react-redux的connect函数
- connect(mapStateToProps.mapDispatchToProps)(组件)
- -mapStateToProps:映射状态,返回的是一个对象
- -mapDispatchToProps:映射操作状态的方法,返回值是一个对象
- connect(mapStateToProps.mapDispatchToProps)(组件)
备注:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
Q36:求和案例-react-redux优化
容器组件和UI组件合并为一个文件
无需自己给容器组件传递store,给
<App/>
包裹一个<Provider store={store}></Provider>
即可使用react-redux后不再需要自己去检测redux状态的变化,容器组件自动帮你完成这个工作
mapDispatchToProps也可以简单的写成一个对象
一个组件要和redux"打交道"要经过那几步?
定义好UI组件–不要暴露
引入react-redux的
connect
方法生成一个容器组件,并暴露,写法如下:connect((state) => ({ count: state }), { jia: createIncrementAction, jian: createDecrementAction, jiaAsync: createIncrementAsyncAction, })(Count);
在UI组件中通过
this.props.xxxx
读取和操作状态
Q37:求和案例-react_redux数据共享版
定义一个Person组件,和Count组件通过redux共享数据
为Person组件编写:reducer、action,配置constant常量
重点:Person的reducer和Count的Reducer要使用combineReducer进行合并,合并是重点
交给store的是总reducer,最后注意在组件中取出状态的时候,记得要“取到位”
Q38:纯函数
一类特别的函数:只要是同样的输入(实参),必须得到同样的输出(返回)
必须遵从以下一些约束
- 不得改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用
Date.now()
或者Math.random()
等不纯的方法
redux的reducer函数必须是一个纯函数
Q39:求和案例_react_redux开发者工具的使用
npm i redux-devtools-extension
.store中进行配置
import { composeWithDevTools } from "redux-devtools-extension";
createStore(reducers,composeWithDevTools(applyMiddleware(thunk)));
Q40:求和案例_react_redux最终版
所有变量名字要规范,尽量触发对象的简写形式
reducers文件中,编写index.js专门用于汇总并暴露所有的reducer
Q41:Hooks
- React Hook/Hooks是什么?
Hook是React 16.8.0版本新增的新特性/新语法
可以让你在函数组件中使用state以及其他React特性
- 三个常用的Hook
State Hook:React.useState()
Effect Hook:React.useEffect()
Ref Hook:React.useRef()
- State Hook
(1)State Hook让函数组件也可以有state状态,并进行状态数据的读写操作
(2)语法:const [xxx,setXxx] = React.useState(initValue)
(3)useState()说明:
参数:第一次初始化指定的值在内部作缓存
返回值:包含2个元素的数组,第一个内部当前状态值,第二个为更新状态的函数
(4)setXxx()2种用法
setXxx(newValue):参数为非函数值,直接指定新的状态值,内部用其覆盖原来的状态值
setXxx(value => newValue):参数为函数,接收原本的状态值,返回新的状态值,内部用其覆盖原来的状态值
- Effect Hook
(1)Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2)React中的副作用操作:
发ajax请求数据获取
设置订阅 / 启动定时器
手动更改真实DOM
(3)语法和说明
useEffect(()=>{
//在此可以执行任何带副作用的操作
return ()=>{
//在此像一些收尾工作,比如清楚定时器/取消订阅等
}
},[stateValue])//如果指定的是[],回调函数只会在第一次render()后执行
(4)可以把 useEffect Hook看做如下三个函数的组合
componentDidMount()
componentDidUpdate()
componentWillUnmount()
- Ref Hook
(1)Ref Hook可以在函数组件中存储/查找组件内的标签或任意其他数据
(2)语法:const refContainer = useRef()
(3)作用:保存标签对象,功能与React.createRef()一样
Q42:Fragment
使用
<Fragment></Fragment>
<></>
Q43:Context
理解:一种用于组件间通信的方式,常用于【祖组件】与【后代组件】间通信
(1)创建Context容器对象
const XxxContext = React.createContext()
(2)渲染子组件时,外面包裹xxxContext.Provider,通过value属性给后代组件传递数据:
<xxxContext.Provide value={数据}>
子组件
</xxxContext.Provide>
(3)后代组件读取数据:
//第一种方式:使用类组件
static conTextType = xxxContext //声明接收context
this.context //读取context中的value数据
//第二种方式:函数组件与类组件都可以使用(不是特别理解这种写法)
<xxxContext.Consumer>
{
value => { //value就是context中的value数据
要显示的内容
}
}
</xxxContext.Consumer>
Q44:组件优化
Component的两个问题
只用执行setState(),即便不改变状态数据,组件也会重新render() ==> 效率低
只要当前组件重新render(),就会自动重新render子组件 ==> 效率低
效率高的做法
只有当前组件的state或者props发生改变的时候才重新render()
原因
Component中的shouldComponentUpdate()总是返回true
解决
办法1
重写shouldComponentUpdate()方法
比较新旧state或props数据,如果有变化才返回true,如果没有返回false
办法2
使用PureComponent
PureComponent重写了shouldComponentUpdate(),只有state或props数据有变化才返回true
注意:
只是进行state和props数据的浅比较,如果只是数据对象内部数据变化了,返回false
不要直接修改state数据,而是要产生新数据
项目中一般使用PureComponent来优化
Q45:render props
如何向组件内部动态传入带内容的结构(标签)?
Vue中:
使用slot技术,也就是通过组件标签传入结构 <A><B/></A>
React中:
使用children props:通过组件标签传入结构
使用render props:通过组件标签属性传入结构,而且可以携带数据,一般用render函数属性
children props
<A>
<B>xxx</B>
</A>
{this.props.children}
问题:如果B组件需要A组件内的数据 ==> 找不到
render props
<A render={(data)=><C data={data}></C>}></A>
A组件:{this.props.render(内部state数据)}
C组件:读取A组件传入的数据展示 {this.props.data}
Q46:错误边界
理解:用来捕获后代组件错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件,定时器中产生的错误
使用方式:
getDerivedStateFromError配合componentDidCatch
//生命周期函数,一旦后台组件出错,就会触发
static getDerivedStateFromError(error){
return {hasError:true}
}
componentDidCatch(error,info){
//统计页面的错误,发送请求到后台去
console.log(error,info)
}
Q47:组件通信方式
组件间的关系
父子组件
兄弟组件(非嵌套组件)
祖孙组件(跨级组件)
几种通信方式
(1)props:
a.children props
b.render props
(2)消息订阅与发布
pubs-sub、event等
(3)集中式管理
redux、dva
(4)conText
生产者-消费者模式
比较好的搭配方式
父子组件:props
兄弟组件:消息订阅-发布、集中式管理
祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发中使用比较少,封装组件使用多)