UI = f(data)
UI = render(props + state)
在 React 组件中,props
(入参)或者 state
(状态)发生改变,UI 也会相应的更新。
组件更新不止来自自身状态的改变,而两个组件之间建立数据上的连接,实现组件间的通信,它的背后是 React 数据流解决方案。下面将会从各方面说明当前实践中 React 数据通信的方式。
基于 props 的单向数据流
React 非常灵活,但它也有一个严格的规则:
而通过 props
通信的的组件也需遵循单向数据流(单向绑定)的原则
所谓单向数据流,是指 props
只能流向组件树中比自己层级更低的组件。React 单向数据流的思想使得组件模块化,易于快速开发。基于 props
这种传参方式,可以轻松实现“父子组件通信”、“子父组件通信”、“兄弟组件通信”。
父子组件通信
父组件直接将 props
传入子组件
?:
function Child(props) {
return <div>{`接收来自父组件的内容:${props.text}`}</div>
}
class Father extends React.Component {
state = {
text: '初始化父组件文本'
}
changeText = () => {
this.setState({
text: '改变后的父组件文本'
})
}
render() {
return (
<div>
<button onClick={this.changeText}>
修改文本
</button>
<Child text={this.state.text} />
</div>
)
}
}
子组件读取了来自父组件 props
的内容,并且在父组件 state.text
更改后保持一致
子父组件通信
由于单向数据流,子组件不能直接将自身数据塞给父组件;但是父组件可以通过 props
传递给子组件一个绑定自身上下文的函数,那么子组件就可以将想要传递给父组件的数据以函数参数的形式交予父组件,从而间接实现子组件向父组件的数据通信
?:
class Child extends React.Component {
state = {
text: '子组件'
}
changeText = () => {
this.props.changeText(this.state.text)
}
render() {
return (
<div>
<button onClick={this.changeText}>点击更新父组件文本</button>
</div>
)
}
}
class Father extends React.Component {
state = {
text: '初始化父组件文本'
}
changeText = (newText) => {
this.setState({
text: newText
})
}
render() {
return (
<div>
<p>{`父组件文本:${this.state.text}`}</p>
<Child changeText={this.changeText} />
</div>
)
}
}
子组件通过 props.changeText
传入自身的 state.text
调用,修改了父组件的 state.text
,从而实现了子父组件通信
兄弟组件通信
兄弟组件共有同一个父组件,可以通过父子组件通信和子父组件通信结合实现兄弟组件通信
?:
function Child1(props) {
return <div>{`接收来自父组件的内容:${props.text}`}</div>
}
class Child2 extends React.Component {
state = {
text: '来自 Child2 的文本'
}
changeText = () => {
this.props.changeText(this.state.text)
}
render() {
return (
<div>
<button onClick={this.changeText}>点击更新 Child1 组件文本</button>
</div>
)
}
}
class Father extends React.Component {
state = {
text: '初始化父组件文本'
}
changeText = (newText) => {
this.setState({
text: newText
})
}
render() {
return (
<div>
{ /** 父子组件通信 */}
<Child1 text={this.state.text} />
{/** 子父组件通信 */}
<Child2 changeText={this.changeText} />
</div>
)
}
}
子组件 Child2
通过 props.changeText
调用更新了父组件的 state.text
(子父通信),子组件 Child1
通过 props.text
获取了父组件的 state.text
(父子通信),从而实现了兄弟组件之间的通信。
不推荐其他场景使用 props
比如多层组件通信,通过 props
层层传递非常简单,但是会编写非常繁琐冗余的代码,并且中间层的组件会引入很多不必要的属性。编写代码的程序员会非常痛苦,而且会是整个项目的维护成本变高。
针对其他场景的组件通信可以通过其他方式实现。
使用 Context API
在 React 16.3 之前,Context API 存在种种局限性,不被 React 官方推荐使用,只作为一个概念探讨。从 React 16.3 之后,对 Context API 进行了改进,使其具备更高的可用性。
Context API 工作流
Context API 有 3 个关键 API:React.createContext
、Context.Provider
、Context.Consumer
React.createContext
const MyContext = React.createContext(defaultValue)
当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件树中离自身最近的那个匹配的 Provider
中读取到当前的 context 值。
只有当组件所处的树中没有匹配到 Provider 时,其 defaultValue
参数才会生效。
Context.Provider
<MyContext.Provider value={}></MyContext.Provider>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。
Provider 接收一个 value
属性,传递给消费组件。一个 Provider 可以和多个消费组件有对应关系。多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据。
当 Provider 的 value
值发生变化时,它内部的所有消费组件都会重新渲染。Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate
函数,因此当 consumer 组件在其祖先组件退出更新的情况下也能更新。
Context.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
一个 React 组件可以订阅 context 的变更,这让你在函数式组件中可以订阅 context。
这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。传递给函数的 value
值等价于组件树上方离这个 context 最近的 Provider 提供的 value
值。如果没有对应的 Provider,value
参数等同于传递给 createContext()
的 defaultValue
。
新的 Context API 解决了什么问题
新的 Context API 改进了这一点:即便组件的 shouldComponentUpdate
返回 false
,它仍然可以“穿透”组件继续向后代组件进行传播,进而确保了数据生产者和数据消费者之间数据的一致性。
"发布-订阅"模式
"发布-订阅"模式是解决通信类问题的"万能钥匙",有着广泛的应用,比如:
-
ajax 的
success
、error
等事件 -
Node.js 中的
EventEmitter
-
Vue.js 的事件总线(
EventBus
)
理解"发布-订阅"
发布-订阅模式其实是一种对象间一对多的依赖关系,当一个对象的状态发送改变时,所有依赖于它的对象都将得到状态改变的通知。
发布-订阅模式的优点是监听事件和触发事件的地方是不受限制的,只要它们在同一个上下文,就可以触发监听。这非常适合任意组件的通信
设计发布-订阅模式 API
针对事件的监听(订阅)和事件的触发(发布):
on
:注册事件的监听,指定事件触发时的回调emit
:触发事件,可以通过参数在触发时携带数据off
:删除监听once
:和on
一样,不过在触发事件后删除监听
实现发布-订阅
class EventEmitter {
constructor() {
this.eventMap = Object.create(null)
}
on(type, handler) {
if(!handler instanceof Function) {
throw new Error('handler 必须是一个函数')
}
this.eventMap[type] ? this.eventMap[type].push(handler) : this.eventMap[type] = [handler]
return this
}
emit(type, params) {
this.eventMap[type] && this.eventMap[type].forEach((handler) => handler(params))
return this
}
off(type, handler) {
if(this.eventMap[type]) {
if(!handler) {
delete this.eventMap[type]
} else {
this.eventMap[type] = this.eventMap[type].filter(c => c !== handler)
}
}
return this
}
once(type, handler) {
this.on(type, (...args) => {
handler(...args)
this.off(type, handler)
})
return this
}
}
const myEvent = new EventEmitter()
const testHandler = (params) => {
console.log(`test 事件被触发了,testHandler 接收的参数:${params}`)
}
myEvent.on('test', testHandler)
myEvent.emit('test', 'emit') // test 事件被触发了,testHandler 接收的参数:emit
myEvent.off('test', testHandler)
myEvent.emit('test', 'emit')
组件通信应用
对于任意两个组件 A 和 B,可以通过 EventEmitter
将数据从 A 传入 B,从而实现组件之间通信
window.myEvent = new EventEmitter()
class A extends React.Component {
state = {
info: '来自A'
}
toB = () => {
window.myEvent.emit('eventKey', this.state.info)
}
render() {
return <button onClick={this.toB}>点击传递 info 到B</button>
}
}
class B extends React.Component {
state = {
params: ''
}
handler = (params) => {
this.setState({
params
})
}
bindHandler = () => {
window.myEvent.on('eventKey', this.handler)
}
render() {
return (
<div>
<button onClick={this.bindHandler}>点我监听 A 的动作</button>
<div>A 传入的内容是:{this.state.params}</div>
</div>
)
}
}
function App() {
return (
<div>
<B />
<A />
</div>
)
}
上面代码的关系图:
第三方数据流框架 -- Redux
对于简单的跨层级组件通信,可以通过 Context API 或者发布-订阅模式解决。但是随着应用复杂度提升,需要维护的状态增多,我们就需要 Redux 来处理。
什么是 Redux
应用中所有的 state 都以一个对象树的形式储存在一个单一的 store 中。 惟一改变 state 的办法是触发 action,一个描述发生什么的对象。 为了描述 action 如何改变 state 树,你需要编写 reducers。
Redux 是如何帮助 React 管理数据的
Redux 主要由三个部分组成:store
、reducer
和 action
-
action
:把数据从应用传到store
的有效载荷(对变化的描述) -
reducer
:一个纯函数,负责对变化的分发和处理,并将新的数据返回给store
-
store
:唯一数据源,而且只读
Redux 工作流:
在 Redux 的整个工作流程中,数据流是严格单向的
如何理解上图工作流:
-
视图(View)的所有数据
state
都来自store
-
对数据修改的唯一途径:派发
action
-
action
会通过reducer
,根据action
的内容对数据进行处理,生产新的state
,最后更新到store
对于组件来说,可以通过约定的方式获取 store
的状态,也可以通过派发 action
修改 store
的状态。
工作流编码
1. 创建 store 对象
import { createStore } from 'redux'
const store = createStore(
reducer,
initialState,
applyMiddleware(middleware1, middleware2, ...)
)
参数:
reducer
- 初始状态
- 中间件
2. 通过 reducer 将新的 state 更新到 store
const reducer = (state, action) => {
// 逻辑处理
return newState
}
3. action 对象通知 reducer 做什么更新
const action = {
type: 'ADD',
payload: 3
}
action
对象允许多个属性,只有 type
是必填属性
type
是 action
的唯一标识,reducer
通过不同的 type
来识别更新不同的 state
,从而能够实现精准的“定向更新”
4. 通过 dispatch 派发 action
import { createStore } from 'redux'
const reducer = (state, action) => {
// 逻辑处理
return newState
}
const store = createStore(reducer)
const action = {
type: 'ADD',
payload: 3
}
store.dispatch(action)
总结
Redux & React 工作流:
现在我们大致了解 Redux 是如何帮助 React 做状态管理,实现灵活的组件间通信
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!