组件化开发(React二)
组件化开发:如果把一个页面的所有处理逻辑放在一起,处理起来会很复杂,因此将一个页面拆分为不同的功能块
- 一个完整的页面可以划分为很多个组件
- 每个组件都用于实现页面的一个功能
- 每一个组件又可以细分
- 组件本身又可以在很多地方复用

React组件:
- 类组件与函数组件
- 根据内部是否有状态需要维护:无状态组件、有状态组件(this.state,不考虑hooks的情况下函数组件是无状态组件,而类组件不定义this.state也可以看做无状态组件)
- 根据组件的不同职责:展示型组件、容器型组件
组件之间有重叠,但都是为了数据逻辑和UI展示的分离
- 函数组件、无状态组件、展示型组件主要关注UI的展示
- 类组件、有状态组件、容器型组件主要关注数据逻辑
类组件与函数组件
类组件
定义要求:首字母大写、继承自React.Component、实现render函数
- constructor可选,在其中初始化数据
- this.state中维护组件内部的数据
- render是类组件唯一必须实现的方法
函数组件(没有hooks的函数组件)
- 没有生命周期、会被挂载、没有生命周期函数
- this关键字不能指向组件实例,因为没有组件实例
- 没有内部状态state
1 2 3 4 5 6 7
| function App(){ return ( <div>Hello World</div> ) } const root = ReactDOM.createRoot(document.querySelector('#root')) root.render(<App/>)
|
生命周期
生命周期:从创建到销毁的整个过程,了解生命周期在最合适的地方完成想要的功能
React的生命周期、生命周期函数
- 装载阶段:组件第一次在DOM树种被渲染的过程——装载完成:componentDidMount
- 更新阶段:组件状态发生变化,重新更新渲染的过程——更新完成:componentDidUpdate
- 卸载阶段:组件从DOM树种被移除的过程——即将移除:componentWillUnmount

各函数任务及时刻
constructor:
- 不初始化state或不进行方法绑定,则可以不实现构造函数
- 作用一:给this.state赋值对象来初始化state
- 作用二:为事件绑定this——调用super()
componentDidMount:
- 组件挂载到DOM Tree后立马调用
- 作用一:进行依赖于DOM的操作
- 作用二:发送网络请求的最好位置
- 作用三:添加订阅
componentDidUpdate:
- 组件更新后立即调用,首次渲染不会执行这个方法
- 作用一:组件更新后,此处进行DOM相关操作
- 作用二:当更新前后props发送了变化,此处发送网络请求
componentWIllUnmount:
- 组件卸载及销毁之前直接调用
- 作用一:进行必要的清理操作
- 作用二:清除timer,取消网络请求或取消上面创建的订阅
不常用生命周期函数
- getDerivedStateFromProps:state 的值在任何 时候都依赖于 props时使用;该方法返回一个对象 来更新state
- getSnapshotBeforeUpdate:在React更新DOM 之前回调的一个函数,可以获取DOM更新前的一 些信息(比如说滚动位置)
- shouldComponentUpdate:该生命周期函数很 常用,但是我们等待讲性能优化时再来详细讲解

组件之间的通信
父组件传值到子组件
- 父组件通过 属性=值 的形式来传递给子组件数据
- 子组件通过props参数获取父组件传过来的值
默认值设置:ChildComponent.defaultProps
参数类型设置:ChildComponent.propTypes isRequired代表是否必须
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from "react"; import ChildComponent from "./component/ChildComponent";
class App extends React.Component{ constructor(){ super() this.state = { message: "I am your father!", info:"You are my child!" } } render(){ let {message,info} = this.state return ( <div> <h2>{message}</h2> <ChildComponent info = {info}/> </div> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React, { Component } from 'react' import { PropTypes } from 'prop-types' export class ChildComponent extends Component {
render() { return ([ <div key="1">{this.props.info}</div>, <div key="2">{this.props.info2}</div> ]) } } ChildComponent.propTypes = { info: PropTypes.string.isRequired } ChildComponent.defaultProps = { info:"Tell me who are you!", info2:"You are my Dad!" } export default ChildComponent
|

子组件传值到父组件
- 通过props给子组件传递一个回调函数,在子组件中调用这个函数
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React from "react"; import ChildComponent from "./component/ChildComponent";
class App extends React.Component{ constructor(){ super() this.state = { message: "I am your father!", info:"You are my child!" } } dataFromChild = function(info){ this.setState({ message:info }) } render(){ let {message,info} = this.state return ( <div> <h2>{message}</h2> <ChildComponent info = {info} dataFromChild = {(message)=> this.dataFromChild(message)}/> </div> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { Component } from 'react' import { PropTypes } from 'prop-types' export class ChildComponent extends Component { sendDataToFather = function(){ this.props.dataFromChild(555) } render() { return ([ <div key="1" onClick = {()=>{this.sendDataToFather()}}>{this.props.info}</div>, <div key="2">{this.props.info2}</div> ]) } } ChildComponent.propTypes = { info: PropTypes.string.isRequired } ChildComponent.defaultProps = { info:"Tell me who are you!", info2:"You are my Dad!" } export default ChildComponent
|
注:各个地方函数的使用要注意,是否需要this,一般传递过去的函数都是箭头函数,调用的函数一般为function
1 2
| <ChildComponent info = {info} dataFromChild = {(message)=> this.dataFromChild(message)}/> <ChildComponent info = {info} dataFromChild = {this.dataFromChild}/>
|
如果直接传递,那么函数的调用由React确定就不知道this是啥
传递过去箭头函数,那么我们不关心什么时候调用这个箭头函数,而箭头函数包含的是隐式绑定的this,所以调用的函数需要function,要不然找不到this
React中实现插槽
为了使组件具有更好的通用性,组件的内容不能限制为固定的div、span等,因此父组件传元素到子组件中,使得元素类型可以多变

通过组件的children子元素实现(父像子传元素)
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React, { Component } from 'react' export class ChildComponent extends Component { render() { const {children} = this.props return ( <div> <div> {children[0]} </div> <div> {children[1]} </div> <div> {children[2]} </div> </div> ) } } export default ChildComponent
|
父组件代码1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React from "react"; import ChildComponent from "./component/ChildComponent";
class App extends React.Component{ constructor(){ super() } render(){ return ( <ChildComponent> <button>按钮</button> <h2>标题</h2> <span>span</span> </ChildComponent> ) } } export default App
|
结果:

更改父组件的子元素
1 2 3 4 5
| <ChildComponent> <span>按钮</span> <i>标题</i> <button>按钮</button> </ChildComponent>
|
结果:

通过props实现插槽(父向子传元素)
- 上面描述过通过props传值到子元素,props可以传的值不仅仅有JavaScript对象,React对象也可以作为参数传过去
- 可以避免通过children获取时索引不能精准的获取传入的元素
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import React from "react"; import ChildComponent from "./component/ChildComponent";
class App extends React.Component{ constructor(){ super() } render(){ const spanJSX = <span>按钮</span> const iJSX = <i>标题</i> const buttonJSX = <button>按钮</button> return ( <ChildComponent spanJSX={spanJSX} iJSX = {iJSX} buttonJSX={buttonJSX}/> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from 'react' export class ChildComponent extends Component { render() { const {spanJSX,iJSX,buttonJSX} = this.props return ( <div> <div>{spanJSX}</div> <div>{iJSX}</div> <div>{buttonJSX}</div> </div> ) } } export default ChildComponent
|
结果:

作用域插槽
- 上面两种插槽方法子组件接受到的都是有父组件写死的元素,但某些时候在子组件中需要根据状态更改组件的内容
- 所以需要实现元素类型父组件决定,数据或状态子组件决定
- 类似于这种由父组件子组件共同协作完成的,通常使用传递函数实现
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React from "react"; import ChildComponent from "./component/ChildComponent";
class App extends React.Component{ constructor(){ super() } render(){ return ( <ChildComponent itemtyPE = {item=><button>{item}</button>}/> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13
| import React, { Component } from 'react' export class ChildComponent extends Component { render() { const {itemtyPE} = this.props const message = "哈哈哈" return ( <div> {itemtyPE(message)} </div> ) } } export default ChildComponent
|

非直接父子组件的通信
常见方法:
- 通过props属性自上而下传递
- 但是对于某些数据需要在多个组件中共享(如地区、用户信息等等)
- 如果在顶层App定义这些信息,一层层传递下去,对于一些中间层不需要这些数据的组件来说,是一种冗余的操作
Context方法:
- context提供了一种在组件之间共享值的方法,而不必显示的通过树的逐层传递props
- context的目的是共享对于组件树来说是“全局的数据”,如当前用户、主题
注册Context
由于后续会在多个组件中用到这个Context1,所以将Context1的注册放到一个js文件中
1 2 3 4 5
| import React from "react";
const Context1 = React.createContext()
export default Context1
|
Context值的设置
父组件设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React from "react"; import ChildComponent from "./component/ChildComponent"; import Context1 from "./context/Context1";
class App extends React.Component{ constructor(){ super() } render(){ return ( <Context1.Provider value={{color:"red",size:18}}> <ChildComponent/> </Context1.Provider> ) } } export default App
|
Context值的获取
后代类组件Context值的获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React, { Component } from 'react' import Context1 from '../context/Context1' import GrandsonComponent from './GrandsonComponent' export class ChildComponent extends Component { constructor(){ super() } render() { return ( <div> <h2 style={{color:this.context.color}}>子组件</h2> <GrandsonComponent/> </div> ) } } ChildComponent.contextType = Context1 export default ChildComponent
|
后代函数组件Context值获取:
由于函数组件中没有this,所以不能够使用上面类组件获取值的方法,使用Context.Consumer获取值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import Context1 from "../context/Context1"
export function GrandsonFunComponent(){ return( <Context1.Consumer> { value=>{ return <h2 style={{color:value.color}}>函数孙子组件</h2> } } </Context1.Consumer> ) } export default GrandsonFunComponent
|
多个Context的注册与获取
注册第二个Context:
1 2 3 4 5
| import React from "react";
const Context2 = React.createContext()
export default Context2
|
单组件设置多Context:
父组件采用Context嵌套的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import React from "react"; import ChildComponent from "./component/ChildComponent"; import Context1 from "./context/Context1"; import Context2 from "./context/Context2";
class App extends React.Component{ constructor(){ super() } render(){ return ( <Context2.Provider value={{color:"blue"}}> <Context1.Provider value={{color:"red",size:18}}> <ChildComponent/> </Context1.Provider> </Context2.Provider> ) } } export default App
|
后代组件获取多Context:
通过设置contextType与Context.Consumer组合获取多个Context
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| import React, { Component } from 'react' import Context1 from '../context/Context1' import Context2 from '../context/Context2' import GrandsonComponent from './GrandsonComponent' import GrandsonFunComponent from './GrandsonFunComponent' export class ChildComponent extends Component { constructor(){ super() } render() { return ( <div> <h2 style={{color:this.context.color}}>子组件</h2> <Context2.Consumer> { value=>{ return <h3 style={{color:value.color}}>子组件2</h3> } } </Context2.Consumer> <GrandsonComponent/> <GrandsonFunComponent/> </div> ) } } ChildComponent.contextType = Context1 export default ChildComponent
|
Context默认值的使用
使用了Context但不是Context.Provider的后代元素
Context的注册(带默认值):
1 2 3 4 5
| import React from "react";
const Context1 = React.createContext({color:"green"})
export default Context1
|
Context的设置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import React from "react"; import ChildComponent from "./component/ChildComponent"; import Context1 from "./context/Context1"; import Context2 from "./context/Context2";
class App extends React.Component{ constructor(){ super() } render(){ return ( <div> <Context2.Provider value={{color:"blue"}}> <Context1.Provider value={{color:"red",size:18}}> <ChildComponent/> </Context1.Provider> </Context2.Provider> <ChildComponent/> </div> ) } } export default App
|
Context的获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React, { Component } from 'react' import Context1 from '../context/Context1' import Context2 from '../context/Context2' import GrandsonComponent from './GrandsonComponent' import GrandsonFunComponent from './GrandsonFunComponent' export class ChildComponent extends Component { constructor(){ super() } render() { return ( <div> <h2 style={{color:this.context.color}}>子组件</h2> <GrandsonComponent/> <GrandsonFunComponent/> </div> ) } } ChildComponent.contextType = Context1 export default ChildComponent
|

任意组件之间的通信——事件总线(EventBus)
事件总线实现
setState的细节原理
为什么要setState:React没有数据劫持,直接更改this.state时,React不知道数据已经发生了变化,需要通过this.setState()来告知React数据发生了变化,需要刷新页面
setState的异步/同步
一般情况下,setSTate是异步的
设置为异步的原因:
- 可以显著提升性能,如果每次调用setState都进行一次更新,那么render会频繁的调用,界面重新渲染,这样效率很低(可以联系到回流-重绘),因此设置为异步获取到数据的多次更新,之后批量更新
- 如果同步更新了state,但还没执行render函数,那么state与子组件的props不能保持同步,可能出现若干问题
setState批量更新方法
- 获取到一系列setState的更新值,按先后顺序把旧的state与新的state合并(添加新的属性、更新改变了的属性),在最后将合并好的state赋值给this.state并刷新到页面上(SCU优化)
- 注意:先合并再赋值给this.state
异步结果的获取:
既然是异步,那么我们便不知道更新啥时候结束,但有些时候又想在更新结束后进行一定的操作,这个操作应该在哪里进行?
方法一:setState的回调函数
- setState接受两个参数:新的state+回调函数
- 回调执行顺序:render->componentDidUpdate(因此也可以在这个生命周期函数中对更新结束后的值操作)->setState回调
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import React from "react";
class App extends React.Component{ constructor(){ super() this.state ={ counter:3 } } buttonClick(){ this.setState({ counter:this.state.counter + 1 },()=>{ console.log("setState回调",this.state) }) this.setState({ counter:this.state.counter + 1 },()=>{ console.log("setState回调",this.state) }) this.setState({ counter:this.state.counter + 1 },()=>{ console.log("setState回调",this.state) }) } componentDidUpdate(){ console.log("componentDidUpdate") } render(){ console.log("render") const { counter } = this.state return ( <div> <h2>{counter}</h2> <button onClick={()=>{this.buttonClick()}}>更新state</button> </div> ) } } export default App
|

注:
- 多次setState合并,一次render,一次componentDidUpdate,多次回调
- 这里多次一个提交了3次+1,但是最后结果只加了1,是因为this.state会在多次提交的state与原始state合并结束后再赋值给this.state,也就是这里获取到的this.state每次都是初始值3,相当于提交了3次 counter=4
this.setState的第一个参数也可以是一个函数:
- 返回值是要更改的值
- 参数是已经合并后的state而不是this.state
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| import React from "react";
class App extends React.Component{ constructor(){ super() this.state ={ counter:3 } } buttonClick(){ setTimeout(()=>{ console.log("setTimeOut宏任务") },0) new Promise((resolve)=>{ resolve("") }).then((res)=>{ console.log("promise微任务") }) this.setState((state)=>{ console.log("setState",state) return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) this.setState((state)=>{ return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) this.setState((state)=>{ return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) } componentDidUpdate(){ console.log("componentDidUpdate") } render(){ console.log("render") const { counter } = this.state return ( <div> <h2>{counter}</h2> <button onClick={()=>{this.buttonClick()}}>更新state</button> </div> ) } } export default App
|
执行顺序为:this.setState第一个参数函数->render->componentDidUpdate->this回调

注:
- 多次setState合并,一次render,一次componentDidUpdate,多次回调
- setState函数执行、render、componentDidUpdate执行都是在微任务后,宏任务前,因此前面的函数应该都是放在微任务队列中
- 提交3次+1,最后得到的结果也和预期相同,因为将3次setState的第一个参数函数放入微任务队列中,在原来的state基础上进行更改,然后合并,得到新的state,在调用下一个微任务setState时,用前面合并好的state作为参数,再进行更改与合并操作
- 虽然setState中是在微任务中分3次调用,但setState的回调是在完成componentDidUpdate再放入微队列中,此时this.state已经更改为最后值,所以获取到的都是最后结果
回调的好处:
- 在数据更改后处理一些state的逻辑
- 回调函数会把之前的state和props传入进来
- 由于setState是一个异步函数,所以对更新后值的处理要放入回调中
方法二:组件的生命周期函数componentDidUpdate
上面已讲
setState的同步
- React18之前,在组件生命周期和React合成事件中,setState是异步
- React18之前,在setTimeout或者原生dom事件中,setState是同步
注:
- 由React去决定运行时机的大多是异步
- 由setTimeout/或者原生Dom的响应函数中调用这种用户编写决定运行时机的一般是同步
React18之后,setState默认都是异步的
React18之后获取同步的setState
使用特殊的flushSync操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| import React from "react"; import {flushSync} from "react-dom"
class App extends React.Component{ constructor(){ super() this.state ={ counter:3 } } buttonClick(){ setTimeout(()=>{ console.log("setTimeOut宏任务") },0) new Promise((resolve)=>{ resolve("") }).then((res)=>{ console.log("promise微任务") })
flushSync(()=>{ this.setState((state)=>{ console.log("setState",state) return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) }) this.setState((state)=>{ return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) this.setState((state)=>{ return {counter:state.counter+1} },()=>{ console.log("setState回调",this.state) }) } componentDidUpdate(){ console.log("componentDidUpdate") } render(){ console.log("render") const { counter } = this.state return ( <div> <h2>{counter}</h2> <button onClick={()=>{this.buttonClick()}}>更新state</button> </div> ) } } export default App
|

可以看到使用flushSync中的setState在微任务执行之前就已经运行,说明flushSync确实将setState更改为同步方式运行
React的SCU优化
React更新机制
React渲染流程:

React更新流程:
React在props或state发生改变时,会调用React的render方法,会创建一颗新树,React会基于新树与旧树的不同去判断如何有效的更新UI
- 如果两棵树进行完全比较更新,算法的时间复杂度为O(n^3),n为树中元素的数量,开销太大,React的更新性能会变得低效
React对diff算法的优化:
- 同层节点之间相互比较,不会跨节点比较
- 不同类型的节点,产生不同的树结构
- 开发中,通过key来指定哪些节点在不同的渲染下保持稳定
keys的优化:
- 在最后位置插入数据,有无key意义不大
- 在前面插入数据,没有key,所有的li都需要更改,有key,后续的li只需要移动
key的注意事项:
- key应该是唯一的
- key不能使用随机数,每次render的随机数不同
- 不能使用index作为key,index在操作后也可能会改变,要使用一个不会随render改变的值
shouldComponentUpdate优化(即SCU优化)

- 在上图中,如果App中值发生了改变,那么App的render函数会重新执行,而在App的执行过程中,会执行其所有子组件的render函数,都进行diff算法,性能很低
- 子组件调用render函数应该有一个前提——依赖的数据state/props发生改变,此时才调用render函数
shouldComponentUpdate函数
- 参数:nextProps(修改后的pros)、nextState(修改之后的state)
- 返回值:true(调用render方法)、false(不调用render方法)、默认返回true(只要state改变就会调用render方法)
在值发生变化时,就可以在shouldComponentUpdate监听自己的state和props是否发生变化从而觉得是否进行render
类组件PureComponent
继承自PureComponent自动实现了SCU优化,不需要自己去编写shouldComponentUpdate函数
如果使用Component,只要调用setState就会执行render函数,而PureComponent会帮我们实现比较setState后state是否改变,如果改变才执行render
不可变数据的力量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import React from "react"; import {flushSync} from "react-dom"
class App extends React.Component{ constructor(){ super() this.state ={ books:[ {name:"aaa",count:1}, {name:"bbb",count:1}, {name:"ccc",count:1}, {name:"ddd",count:1} ] } } addNewBook(){ const newBook = {name:"eee",count:1} this.setState({ }) }
render(){ console.log("render") const { books } = this.state return ( <div> <ul> {books.map((item,index)=>{ return( <li key={index}> <span>name:{item.name}-count:{item.count}</span> <button>+1</button> </li> ) })} </ul> <button onClick={e=>this.addNewBook()}>添加新书</button> </div> ) } } export default App
|
上述在setState中没进行任何操作,但render依然会执行,而改成PureComponent时不会执行
PureComponent中render执行要求:
- PureComponent在比较时底层会使用shallowEqual方法,shallowEqual是一种浅层比较,如果比较的两个对象是同一个,则不会调用render,即使this.state发生了改变,也不会调用render
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| constructor(){ super() this.state ={ books:[ {name:"aaa",count:1}, {name:"bbb",count:1}, {name:"ccc",count:1}, {name:"ddd",count:1} ] } } addNewBook(){ const newBook = {name:"eee",count:1} this.state.books.push(newBook) const books = this.state.books this.setState({ books:books }) }
|
- 如果要想在这种情况下调用render,可以利用books创建一个新的对象,用这个对象去更改state,在这种情况下,即使未更改this.state的,也会调用render
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| constructor(){ super() this.state ={ books:[ {name:"aaa",count:1}, {name:"bbb",count:1}, {name:"ccc",count:1}, {name:"ddd",count:1} ] } } addNewBook(){ const books = [...this.state.books] this.setState({ books:books }) }
|
函数组件memo
函数由于不能继承于PureComponent,可以使用memo替代,即用memo包裹原来的函数组件

获取原生DOM或获取组件
在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作:
- 管理焦点,文本选择或媒体播放;
- 触发强制动画;
- 集成第三方 DOM 库;
- 我们可以通过refs获取DOM;
通过refs获取DOM的方式:
- 传入字符串:使用时通过 this.refs.传入的字符串 格式获取对应的元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| import React from "react";
class App extends React.PureComponent{ constructor(){ super() } addNewBook(){ console.log(this.refs.button) }
render(){ return ( <div> <button onClick={e=>this.addNewBook()} ref="button">查看DOM</button> </div> ) } } export default App
|
- 传入一个对象:通过React.createRef()方式创建一个对象,使用使用对象的current属性来访问DOM
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| import React from "react";
class App extends React.PureComponent{ constructor(){ super() this.titleRef = React.createRef() } addNewBook(){ console.log(this.titleRef.current)
}
render(){ return ( <div> <button onClick={e=>this.addNewBook()} ref={this.titleRef}>查看DOM</button> </div> ) } } export default App
|
- 传入一个函数:该函数会在DOM被挂载时回调,回调函数会传入一个元素对象,我们可以访问并保存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| import React from "react";
class App extends React.PureComponent{ constructor(){ super() this.titleRef = React.createRef() } render(){ return ( <div> <button ref={(e)=>{console.log(e)}}>查看DOM</button> </div> ) } } export default App
|
通过refs获取组件的方式:
针对类组件:
- 上面的ref同样可以加在子组件上,获取得到子组件的实例,就可以在父组件中获取子组件对子组件的函数、变量进行操作
针对函数组件:
- 函数组件由于没有实例,是不可能获取到它的
- 但是在某些情况,想获取得到函数实例中的某个DOM元素,可以使用foreardRef
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import React from "react"; import { forwardRef } from "react";
const HelloWorld = forwardRef(function(props, ref){ return ( <div> <h1 ref={ref}>Hello World</h1> </div> ) }) class App extends React.PureComponent{ constructor(){ super() this.HelloWorldRef = React.createRef() } render(){ return ( <div> <HelloWorld ref = {this.HelloWorldRef}/> <button onClick={()=>{console.log(this.HelloWorldRef.current)}}>查看DOM</button> </div> ) } } export default App
|
受控组件与非受控组件
- HTML中,表单元素(input、textarea、select)通常会自己维护state并根据用户的输入进行更新
- React中,可变状态通常保存在组件的state属性中,并且只能通过setState来更新
- 在JSX中,如果给表单元素设置了value属性,显示this.state.value的值,那么表单元素就不会自己维护state,其state来源于React的state,因此当设置了value属性后,input元素输入后输入框里的东西不会变化,相当于未输入
- 此时只能给input元素添加onChange(e)监听,从而在监听函数中使用输入的值在setState中更改value的值,使得页面刷新
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import React from "react"; import { forwardRef } from "react";
class App extends React.PureComponent{ constructor(){ super() this.state ={ username:"oww" } } inputChange(e){ this.setState({username:e.target.value}) } render(){ const {username} = this.state return ( <div> <input type="text" name="username" value={username} onChange={(e)=>{this.inputChange(e)}}/> </div> ) } } export default App
|
计算属性名
当对多个表单元素变成受控元素时,其更新需要多个监听,此时可用计算属性名将这些监听抽取到一个函数中
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import React from "react"; import { forwardRef } from "react";
class App extends React.PureComponent{ constructor(){ super() this.state ={ username:"oww", password:"" } } inputChange(e){ this.setState({[e.target.name]:e.target.value}) } render(){ const {username,password} = this.state return ( <div> <input type="text" value={username} name="username" onChange={(e)=>{this.inputChange(e)}}/> <input type="text" value={password} name="password" onChange={(e)=>{this.inputChange(e)}}/> </div> ) } } export default App
|
checkbox表单多选监听
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| import React from "react";
class App extends React.PureComponent{ constructor(){ super() this.state ={ hobbies:[ {id:"sing",text:"唱",checked:false}, {id:"jump",text:"跳",checked:false}, {id:"rap",text:"rap",checked:false} ] } } inputChange(e,index){ const hobbies = [...this.state.hobbies] hobbies[index].checked = e.target.checked this.setState({ hobbies:hobbies }) } render(){ const {hobbies} = this.state return ( <div> <h2>爱好列表</h2> {hobbies.map((item, index)=>{ return ( <label htmlFor={item.id} key={item.id}> <input type="checkbox" id= {item.id} checked={item.checked} onChange={(e)=>this.inputChange(e,index)}/>{item.text} </label> ) })} </div> ) } } export default App
|
select多选
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| import React from "react";
class App extends React.PureComponent{ constructor(){ super() this.state ={ fruits:[ {id:"apple",text:"苹果"}, {id:"banana",text:"香蕉"}, {id:"orange",text:"橘子"} ], checked:["apple"] } } selectChange(e){ let checked = Array.from(e.target.selectedOptions).map(item=>item.value) this.setState({ checked:checked }) } render(){ const {fruits,checked} = this.state return ( <div> <h2>爱好列表</h2> <select multiple value={checked} onChange={e=>this.selectChange(e)}> {fruits.map(item=>{ return <option value={item.id} key={item.id}>{item.text}</option> })} </select> </div> ) } } export default App
|
高阶组件
高阶函数:满足下列一个条件
高阶组件(Higher-Order Components、HOC):
- 高阶组件的参数为组件,返回值为新组件的函数
- 高阶组件本身不是一个组件,而是一个函数
高阶组件不是React的API,而是基于React组合特性而形成的一种设计模式
高阶组件应用——props的增强
HOC代码:高阶函数包裹代码
例如本代码可以将用户信息封装传给子组件,不需要像上面的父向子传值,在子组件中写其他代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| import { PureComponent } from "react"
function enhanceProps(WrapperCpn, otherProps){ class UserComponent extends PureComponent{ constructor(){ super() this.state = { userinfo:{ username:"ouwenwu", age:22 } } } render(){ return <WrapperCpn {...this.state.userinfo} {...otherProps}/> } } return UserComponent } export default enhanceProps
|
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import React from "react"; import ChildComponent from "./component/ChildComponent"; import enhanceProps from "./HOC/HOC";
class App extends React.PureComponent{ constructor(){ super() } render(){ const EnhancePropsCpn = enhanceProps(ChildComponent,{color:"blue"}) return ( <div> <EnhancePropsCpn/> </div> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from 'react' export class ChildComponent extends Component { constructor(){ super() } render() { return ( <div> <h2 style={{color:this.props.color}}>username:{this.props.username}</h2> </div> ) } } export default ChildComponent
|
结果:

高阶组件应用——Context的共享
HOC代码:HOC可以返回类组件也可以返回函数组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { PureComponent } from "react" import Context1 from "../context/Context1"
function enhanceProps(WrapperCpn){ return props=>{ return ( <Context1.Consumer> { value=>{ return <WrapperCpn {...value} {...props}/> } } </Context1.Consumer> ) } } export default enhanceProps
|
父组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import React from "react"; import ChildComponent from "./component/ChildComponent"; import Context1 from "./context/Context1"; import enhanceProps from "./HOC/HOC";
class App extends React.PureComponent{ constructor(){ super() } render(){ const EnhancePropsCpn = enhanceProps(ChildComponent) return ( <div> <Context1.Provider value={{color:"red"}}> <EnhancePropsCpn/> </Context1.Provider>
</div> ) } } export default App
|
子组件代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| import React, { Component } from 'react' export class ChildComponent extends Component { constructor(){ super() } render() { return ( <div> <h2 style={{color:this.props.color}}>username:</h2> </div> ) } } export default ChildComponent
|
结果:

注:可以看出对Context不再需要在每一个子组件中写之前提到的获取Context的方法,只需要将所有需要该Context的子组件包裹在高阶组件之中
高阶组件应用——渲染判断定权
与上面的主要差距为,会对传入的参数进行判断来决定返回哪个子组件

高阶组件应用——生命周期劫持
在高阶组件中劫持子组件的生命周期,在自己的生命周期中完成逻辑

总结
高阶组件的主要作用是提高代码的复用性。提高组件之间的复用
缺陷:
- HOC需要在原组件上进行包裹或者嵌套,如果大量使用HOC,将产生非常多的嵌套
- HOC可以劫持props,在不遵从约定的情况下可能造成冲突