父传子props
- 父组件通过
属性 = 值
的形式来传递给子组件数据
jsx
const banners = ['新歌曲', '新MV', '新歌单']
<MainBanner banners={banners}/>
- 子组件通过
props
参数获取父组件传递过来的数据,会自动保存到this.props
中
jsx
// 类组件使用props
class MainBanner extends Component {
render() {
return (
<div>
<ul>
{this.props.banners.map(item=>{
return <li key={item}>{item}</li>
})}
</ul>
</div>
)
}
}
// 函数组件使用props
function MainBanner(props) {
return (
<div>
<ul>
{props.banners.map(item=>{
return <li key={item}>{item}</li>
})}
</ul>
</div>
)
}
- 使用
propTypes
对传入的数据进行类型限制
javascript
// MainBanner.jsx
import PropTypes from 'prop-types'
class MainBanner extends Component {
static propTypes = {
// 限制传入的数据必须是array类型
banners: PropTypes.array
// 限制传入的数据是string且必传
title: PropTypes.string.isRequired
}
}
props
使用默认值
javascript
class MainBanner extends Component {
static defaultProps = {
banners: [],
title: '轮播图'
}
}
propTypes
使用方式文档 :zh-hans.legacy.reactjs.org/docs/typech...
子传父触发函数
- 同样通过
props
父组件给子组件传递一个回调函数,在子组件中调用这个函数即可 - 在父组件中向子组件传递函数
jsx
import { Component } from 'react'
import Count from './components/Count'
class App extends Component {
constructor(){
super()
this.state = {
count: 100
}
}
handleCountAdd(count) {
this.setState({
count: this.state.count + count
})
}
render() {
const { count } = this.state
return (
<div className='app'>
<h1>当前计数:{count}</h1>
{/* 父组件传递函数到子组件 */}
<Count countAddClick={(count) => this.handleCountAdd(count)}/>
</div>
)
}
}
- 在子组件接收该函数,并调用
jsx
import { Component } from 'react'
import propsTypes from 'prop-types'
class Count extends Component {
// 子组件接收函数类型传递
static propsTypes = {
countAddClick: propsTypes.func
}
render() {
// 拿到父组件的函数进行调用
const { countAddClick } = this.props
return (
<div>
<button onClick={e=>countAddClick(1)}>+1</button>
<button onClick={e=>countAddClick(5)}>+5</button>
<button onClick={e=>countAddClick(10)}>+10</button>
</div>
)
}
}
兄弟互传事件总线
- 对于兄弟组件间的通信,可以使用
eventBus
发送和监听事件实现 React
中需要安装一个第三方库events
,用于创建全局的eventBus
shell
npm i events
- 创建
eventBus.js
文件,用于创建一个eventBus
实例
javascript
import { EventEmitter } from 'events'
const eventBus = new EventEmitter()
export default eventBus
- 在需要发送事件的组件中,使用
eventBus.emit(事件名, 发送的数据)
发送事件
javascript
import eventBus from './utils/eventBus'
eventBus.emit('click', { name: 'tony', age: 18 })
- 在需要监听事件的组件中,使用
eventBus.on(事件名, 回调函数)
实现监听,并在回调函数中编写逻辑
javascript
import eventBus from './utils/eventBus'
// 在组件挂载完成后监听事件
componentDidMount() {
eventBus.on('click', this.onClickCallBack.bind(this))
// 注意这里的回调函数需要显示绑定this,否则回调函数中的this为undefined
}
onClickCallBack(payload) {
console.log('监听click事件', payload); // {name: 'tony', age: 18}
}
- 在组件销毁时,需要同时将监听的事件关闭,使用
eventBus.on(事件名, 回调函数)
javascript
componentWillUnmount() {
eventBus.off('click', this.onClickCallBack)
}
深层组件数据共享
- 对于更深层组件而言,如果在顶层组件定义数据,然后逐层传递下去,对于一些中间层不需要数据的组件来说是一种冗余的操作
Vue
中提供了provice
/inject
解决深层传递数据的现象,而React
提供了称为Context
的API
Context
的作用和目的
- 作用: 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递
props
- 目的: 为了共享那些对于组件树而言是"全局"的数据
React.createContext
createContext
用于创建一个需要共享的Context
对象,接收一个参数defaultValue
defaultValue
是组件在顶层查找过程中未找到对应的Provider
所使用的默认值
javascript
React.createContext(defaultValue)
- 首先新建一个
context.js
文件,在里面创建一个全局共享的Context
javascript
import { createContext } from 'react'
const ThemeContext = createContext()
export default ThemeContext
Provider
- 每个
Context
对象会提供一个Provider
组件,用于向其后代组件提供共享的数据,并且Provider
组件接收一个value
属性,值是传递给后代组件的数据 - 多个
Provider
可嵌套使用,里层的会覆盖外层相同的数据 - 当
Provider
的value
值发生变化时,其内部所有后代组件都会重新渲染 - 若某个后代组件订阅了
Context
,该组件会从离自身最近的Provider
中读取当前提供的数据
jsx
import ThemeContext from './context'
class App extends Component {
render() {
return (
<div>
{/* 后代组件须包裹在其Provider子组件中,寓意提供者 */}
{/* value是需要提供的数据 */}
<ThemeContext.Provider value={{ color:'red', size:30 }}>
<Home/>
</ThemeContext.Provider>
</div>
)
}
}
contextType
- 类组件实例中有一个静态属性
contextType
,需赋值为当前创建的Context
对象 - 使用
this.context
来消费最近Provider
提供的数据
jsx
import ThemeContext from './context'
// HomeInfo是Home的子组件
class HomeInfo extends Component {
// 对挂载在类上的contextType重新赋值
static contextType = ThemeContext
render() {
// 获取最近Context.Provider中的数据
const { color, size } = this.context
return (
<div>
<h1>color:{color}</h1>
<h1>size:{size}</h1>
</div>
)
}
}
Consumer
- 函数式组件没有
this.context
,也没有contextType
属性,需使用其提供的Consumer
子组件包裹
- 并且使用函数作为子元素,函数的参数则是
Provide
提供的数据
jsx
function HomeInfo() {
return (
<div>
{/* 使用Consumer子组件包裹,寓意消费者 */}
<ThemeContext.Consumer>
{
{/* 函数作为子元素 */}
({color, size}) => (
<div>
<h2>color:{color}</h2>
<h2>size:{size}</h2>
</div>
)
}
</ThemeContext.Consumer>
</div>
)
}
理解Context
Context
提供了一种在组件之间共享数据的方式,不必显示地通过组件树逐层传递props
- 可以把
Context
当作特定组件树内共享的store
,用于做数据传递
JavaScript作用域链规则
- JavaScript 代码块在执行期间,会创建一个相应的作用域链
- 该作用域链记录着运行时的 JavaScript 代码块,在执行期间所能访问的活动对象,包括变量和函数
- JavaScript 程序通过作用域链可以访问到代码块内部和外部的变量和函数
将
Context
类比作用域链
- 以 JavaScript 作用域链作为类比,
Context
对象好比一个提供给后代组件访问的作用域 ,其属性可以看成作用域上的活动对象 - 由于
createContext
创建出一个context
上下文对象,使用其提供的Provider
对所有后代组件进行包裹,并提供数据 - 所以后代组件可以类似通过作用域链一样,使用
context
上下文访问组件自身外部的变量和函数
React
并不推荐优先考虑使用Context
Context
应用场景: 很多不同层级的组件需要访问同样的数据Context
带来的影响: 使得组件的复用性变差- 如果只是想避免层层传递属性,组件组合有时候是比
Context
更好的解决方案