目录:
1 React的组件化开发
2 React组件生命周期
3 React组件间的通信
4 React组件插槽用法
5 React非父子的通信
6 setState的使用详解
一、React的组件化开发
这里可以以2种方式导出内容:import { XXX } from "" 和import XXX from "",原因看下图的导出方式,这里用到的{ }不是解构。
this.props和this.state发生改变时,就会调用render,
注意:1、render可以return react元素,这个元素就是标签,这样子可以做到插槽效果,父组件传递给子组件。可以被传递。render(){ return (<div></div>)}
2、返回数组的时候会自动遍历数组里面的东西然后渲染出来,比如["asd","qwe","ert"]会在页面中直接展示asdqweert,如果是[<h1>123</h1>,<h2>222</h2>,<h3>333</h3>]就会直接把里面的标签渲染出来。
render(){ return [<h1>123</h1>,<h2>222</h2>,<h3>333</h3>]}
3、如果渲染布尔值或null和undefined都不会渲染东西出来。
render(){ return undefined}
因为函数式组件不能用this关键字,所以不能使用this.state={}、this.setState({})等的方法。
函数式组件没有构造器,所以不能进行状态维护(后面讲到hook的时候就可以做到),就是说,在这个函数里面定义的变量,在每次调用的时候就会初始化变量,不保存你修改变量后的值。
早期的函数组件就是拿来展示内容的,传递一个变量到函数里然后展示这个变量。复杂的东西都是去类组件。有了hook之后就不一样了。
二、React组件生命周期
生命周期主要讲的是类组件的
类实例:定义了一个类组件然后创建了许多个标签,创建实例
生命周期:挂载期间会执行:1、constructor构造函数;2、render渲染函数;3、componentDidMount函数。
当数据发生变化的时候:1、某些方法调用了setState等方法的时候,会引起render函数的调用重新渲染界面;2、然后就会执行componentDidUpdate函数。
当组件被卸载的时候:(这个作用之一是为了关闭卸载组件的某些事件监听,不怎么做的话会内存泄漏):直接调用componentDidWillUnmount函数。
shouldComponentUpdate函数就是控制组件要不要重新渲染界面的,return false 的话就是不重新渲染。
三、React组件间的通信
下载插件方便开发:
进入插件快捷输入的表格网站:(点击snippets)
react 父组件传递给子组件数据:
这里面constructor有保存数据的作用,子组件想要使用父组件传递过来的参数就需要使用 this.props的方法并在constructor和super里面写props,this.props方法和this.state的使用方法差不多。
这里的constructor方法是可以不用写也能做到使用父组件传递过来的参数,因为默认就有做这个props。例如:(不写constructor)
props类型校验的使用方法:1、引入对应的库,2、写需要校验的变量对应的限制
图片中的Greeting是指文件名,name是父组件传递给子组件的变量。
限制父组件传递给子组件的限制可以是:1、数组,2、Boolean,3、函数,4、数字,5、对象6、字符串,7、标识符8、node结点类型(包括element,注释,文本),9、react元素,10、自定义组件,11、实例,12、选定好的几个枚举类型、13几种类型中的任意一个类型。其余类型可以去官网高级查找。
子组件默认值写法以及es13新特性的子组件默认值的写法:
//父组件中:
import React, { Component } from 'react'
import Header from './header'
export class App extends Component {
constructor(){
super()
this.state ={
count:10
}
}
//定义一个给子组件使用的函数
addCount(count){
this.setState({count:this.state.count+count})
}
render() {
return (
<div>{this.state.count}
{/* 传给子组件的addCount可以任意取名字 */}
<Header addCount={(count)=>this.addCount(count)} />
</div>
)
}
}
export default App
//子组件中:
import React, { Component } from 'react'
export class header extends Component {
//不用写constructor也能使用props的内容
//子组件需要再写一个函数,在这个函数里面使用this.props来获得从父组件获得的函数
add(count){
this.props.addCount(count)
}
sub(count){
this.props.addCount(count)
}
render() {
return (
<div>
<button onClick={()=>this.add(1)}>+1</button>
<button onClick={()=>this.add(5)}>+5</button>
<button onClick={()=>this.add(10)}>+10</button>
<button onClick={()=>this.sub(-1)}>-1</button>
<button onClick={()=>this.sub(-5)}>-5</button>
<button onClick={()=>this.sub(-10)}>-10</button>
</div>
)
}
}
export default header
//父组件代码:
import React, { Component } from 'react'
import Header from './header'
export class App extends Component {
constructor(){
super()
this.state ={
tabList:["表单一","表单二","表单三"],
tabIndex:0
}
}
// 父组件给子组件传递的自定义函数,用于子组件和父组件传递参数
onChangeCurrentIndex(index){
this.setState({tabIndex:index})
//这里需要异步才能获取最新的变量值,毕竟是通过setState改变参数
setTimeout(() => {
console.log('tabIndex'+this.state.tabIndex)
}, 0);
}
render() {
const {tabList,tabIndex} = this.state
return (
<div>
<Header tab={tabList} tabIndex={(index)=>this.onChangeCurrentIndex(index)} />
<h1>{tabList[tabIndex]}</h1>
</div>
)
}
}
export default App
//子组件代码:
import React, { Component } from 'react'
import './style.css'
export class header extends Component {
constructor(){
super()
this.state={
currentIndex:0
}
}
itemClick(index){
this.setState({currentIndex:index})
//使用父组件传过来的自定义函数
this.props.tabIndex(index)
setTimeout(() => {
console.log(this.state.currentIndex)
}, 0);
}
render() {
const {tab} = this.props
const {currentIndex} = this.state
return (
<div>header
<div className='tab'>
{tab.map((item,index)=>{
return <span
className={`item ${currentIndex ===index ?'active':''}`}
key={index}
onClick={()=>this.itemClick(index)}
><span className='text'>{item}</span></span>
})}
</div>
</div>
)
}
}
export default header
//css样式:
.tab{
display: flex;
}
.tab .item{
flex: 1;
text-align: center;
}
.tab .item.active{
color: red;
}
.tab .item.active .text{
padding: 0 5px;
border-bottom:2px solid red;
}
四、React组件插槽用法
利用children来实现插槽需要注意:当父组件传递子组件的react元素是多个的时候,children在子元素的props里面是以数组形式存在的;当父组件传递给子组件的react元素是单个的时候,children在子元素的props里面是对象形式存在的,这个时候如果你还在使用children[xx]的话就会报错了,因为对象不是数组。当然单个使用react元素的话是按照对象格式获取。还有一个弊端是索引必须记住放在哪里,否则使用会出错。
建议使用下面这个父组件向子组件传递react元素的形式来做到插槽效果:
组件的作用域插槽:(既父组件传递给子组件不同的标签,而标签里面的动态数据需要从子组件的变量获取。这时候父组件获取不到子组件变量就需要使用作用域插槽)
在react中作用域插槽需要通过父组件传递给子组件函数的形式来完成,具体如下:
父组件:(写一个itemType的函数传递给子组件,其中button是自己选择的标签,item是参数)
父组件也可以编写复杂的判断来给予不同的标签:
子组件:(调用函数就可以)
五、React非父子的通信(了解)
第一种解决非父子通信的方法:当父组件传递给子组件的变量是对象类型时(不加...的话由于{}不能装对象类型,然后就会报错),可以通过...的格式传递,如下图。在子组件中两种方法的调用方法一样。
第二种解决非父子通信的方法:(这种方法也不是很好用,后期用redux的时候方便很多)
使用context的四步骤:
1、创建一个js文件,里面写入( ThemeContext自定义的变量名)
2、import引入到父组件,然后编写特定标签,并在标签里面写好value值:
3、import引入到子组件:(第三步只能设置一个,不可以多个)4、使用参数:
以上是类组件的使用context的方法,下面要用的是函数组件使用context 的方法:
1、引入编译context的文件,2、使用.consumer的标签,3、以函数的形式调用变量
当多个嵌套context标签的时候也需要使用.consumer:(所以,.consumer在函数式组件和类组件都能使用,功能却不一样)
上图中的home自定义组件可以使用两个上下文的value变量
上图的context文件的默认值(value)是给不在usercontext.provider标签内的自定义组件使用的
重要:不同组件间的通信:(将事件和参数都传递到目标组件,任意组件都可以发送事件和参数,任意组件也能监听事件的发生从而触发回调函数并拿到参数)
1、事件总线:npm install hy-event-store
2、创建util文件夹,在文件夹里面编写js文件,内容写入:
3、在发送事件和参数的组件里面import引入eventBus,利用eventBus.emit(事件名称,后续都是参数值)
4、在需要监听的组件里面先import引入eventBus并在componentDidMount生命周期内监听eventBus.on(事件名称,回调函数),在componentWillUnmount移出监听
移出监听在componentWillUnmount:
上图中的bannerPrevClick函数在被eventBus.on(事件名称,回调函数)回调的时候,this指向为undefined,而这个时候我们是需要通过this修改state的变量的。这里有三种方法传递this给回调函数:
1、利用bind绑定this
2、利用箭头函数
3、利用eventBus.on(事件名称,回调函数,绑定this)
六、setState的使用详解(性能优化)
为什么要有setState?因为vue源码是有数据劫持,react没有数据劫持
为什么用基本用法setState修改一个变量,其他变量不会被覆盖消失?因为setState里面有记录新的参数和旧的参数,然后进行合并,而不是直接覆盖。
setState用法:
1、传入对象修改state里的变量
2、传入回调函数
3、setState在修改完state的变量值或函数时,由于是异步的,所以state的值真正改变的值是不能通过this.state马上获取,这时可以传入第二个回调函数来在你修改完state的变量值或函数后进行调用第二个回调函数。
面试常问:为什么setState是异步更新的?2点原因(主要看上图总结)
1、对于相同的setState操作,在异步的情况下,只会执行一次,最后的counter只会加一次一,render函数只执行一次。看下面这张图
如果想要执行三次相同的setState,那么想要写成函数形式:此时counter会加三次1,但是render函数还是只执行一次。setState里面使用函数参数state都是获取最新的state的值,所以才能做到累加效果。看下面这张图
设置为异步的原因1:在一个方法里面多次调用setState的时候由于会执行多次render渲染UI界面,导致性能上的影响,所以使用异步的方法,可以统一获取setState然后一次性执行,避免多次执行render。
设置为异步的原因2:如果setState同步更新本组件的state的值,那么state的值会马上更新,但是render函数的里面的子组件如果用到了state里面的值,由于state更新了,render函数还没更新,导致子组件显示的变量是修改前的,本组件却是最新的,数据不一致。成为异步之后,发生setState的时候立马先更新render,然后再更新state,让这两件事在接近的时刻完成更新。不是完全同步的,因为单线程,只是看起来是完全同步。