Reac组件化以及组件通信
Posted 面条请不要欺负汉堡
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Reac组件化以及组件通信相关的知识,希望对你有一定的参考价值。
一.模块与组件以及模块化与组件化慨念
- 模块:向外提供特定功能的JS文件,便于复用JS,简化JS,提升JS效率数据、对数据的操作(函数)、将想暴露的私有的函数向外暴露(暴露的数据类型是对象)
2. 模块化:形容项目编码方式,即按模块编写与组织的项目。
3. 组件:用来实现特定布局功能效果的代码与资源集合,包含html、css、js、图片资源等,例如一个页面中头部区域的资源集合
4. 组件化:形容项目的编码方式,即按组件方式编写实现的项目。
二.组件化介绍
(1)React的组件相对于Vue更加的灵活和多样,按照不同的方式可以分成很多类组件:
- 根据组件的定义方式,可以分为:函数组件(Functional Component )和类组件(Class Component);
- 根据组件内部是否有状态需要维护,可以分成:无状态组件(Stateless Component
)和有状态组件(Stateful Component) - 根据组件的不同职责,可以分成:展示型组件(Presentational Component)和容器型组件(Container
Component);
(2)这些概念有很多重叠,但是他们最主要是关注数据逻辑和UI展示的分离:
- 函数组件、无状态组件、展示型组件主要关注UI的展示;
- 类组件、有状态组件、容器型组件主要关注数据逻辑;
(3)当然还有很多组件的其他概念:比如异步组件、高阶组件等。
三.各类组件介绍
(一) 类组件
类组件的定义要求
- 组件的名称是**大写字符开头**(无论类组件还是函数组件)
- 类组件需要继承自 React.Component
- 类组件必须实现render函数
在ES6之前,可以通过create-react-class 模块来定义类组件,但是目前官网建议我们使用ES6的class类定义。
使用class定义一个组件
- constructor是可选的,我们通常在constructor中初始化一些数据;
- this.state中维护的就是我们组件内部的数据
- render() 方法是 class 组件中唯一必须实现的方法
// AntdTest 组件名
export default class AntdTest extends Component
render()
return (
<div>
<Button type="primary">按钮</Button>
</div>
)
注意:render函数的返回值
当 render 被调用时,它会检查 this.props 和 this.state 的变化并返回以下类型之一
1.react 元素:div还是<MyComponent>均为 React 元素
2.数组或 fragments:使得 render 方法可以返回多个元素。
3.Portals:可以渲染子节点到不同的 DOM 子树中。
4.字符串或数值类型:它们在 DOM 中会被渲染为文本节点
5.布尔类型或 null:什么都不渲染
(二) 函数组件
函数组件是使用function来进行定义的函数,这个函数会返回和类组件中render函数返回一样的内容。
特点
- 没有生命周期,也会被更新并挂载;
- 没有this(组件实例);
- 没有内部状态(state);
// 函数类型的组件
export function Welcome1(props)
return <div>Welcome1, props.name</div>
(三) 无状态组件(展示组件)
React是通过更新状态,来实现页面的局部动态变化。那么对于一些只做展示的组件,或者一些原子组件,比如说列表中的某一行,一个输入框等,就可以使用无状态组件。由于无状态组件内部不存在状态,因此也可以使用函数式组件进行书写。甚至如果功能比较简单,可以直接使用箭头函数书写。一些UI组件库使用的大多数都是无状态组件。
简洁概括
- 主要用来定义模板,接收来自父组件props传递的数据。
- 使用props.xxx的表达式把props放入模板中
- 无状态模板应该保持模板的纯粹性,以便于组件复用。
//无状态组件
const Child = (props)=>(
<input type="text" value=props.value onChange=props.fun/>
)
或
const PureComponent = (props) => (
<div>
//use props
</div>
)
特征
- 无状态组件无法访问生命周期的方法,因为它不需要组件生命周期和状态管理。
- 无状态组件不会被实例化【无实例化过程不需要分配多余的内存,所以性能更优;同样,由于无实例化也导致了无法访问this】
注意:函数组件,展示组件属于无状态组件
(四) 有状态组件
在无状态组件的基础上,如果组件内部包含状态(state)且状态随着事件或者外部的消息而发生改变的时候,这就构成了有状态组件。有状态组件通常会带有生命周期(lifecycle),用以在不同的时刻触发状态的更新。这种组件也是通常在写业务逻辑中最经常使用到的,根据不同的业务场景组件的状态数量以及生命周期机制也不尽相同。
概括
- 主要用来定义交互逻辑和业务数据。
- 使用this.state.xxx的表达式把业务数据挂载到容器组件的实例上,然后传递props到展示组件上,展示组件接收到props,把props放入到模板中
class StatefulComponent extends Component
constructor(props)
super(props);
this.state =
//定义状态
componentWillMount()
//do something
componentDidMount()
//do something
... //其他生命周期
render()
return (
//render
);
注意:类组件,容器组件 属于有状态组件
(五) 容器组件
在具体的项目实践中,我们通常的前端数据都是通过Ajax请求获取的,而且获取的后端数据也需要进一步的做处理。为了使组件的职责更加单一,引入了容器组件(Container Component)的概念。我们将数据获取以及处理的逻辑放在容器组件中,使得组件的耦合性进一步地降低。
/ 容器组件
export default class CommentList extends Component
constructor(props)
super(props)
this.state =
comments: [],
componentDidMount()
var _this = this
axios.get('/path/to/commentsApi').then(function (response)
_this.setState( comments: response.data )
)
render()
return (
<div>
this.state.comments.map((c, i) => (
<Comment key=i ...c />
))
</div>
)
如上面这个容器组件,就是负责获取用户数据,然后以props的形式传递给UserList组件来渲染。容器组件也不会在页面中渲染出具体的DOM节点,因此,它通常就充当数据源的角色。目前很多常用的框架,也都采用这种组件形式。如:React Redux的connect(), Relay的createContainer(), Flux Utils的Container.create()等。
(六) 展示组件
只做展示一些数据的信息的组件
// 展示组件
function Comment( data )
return (
<div>
' '
<p>data.body</p> <p> --- data.author</p>' '
</div>
)
展示组件 vs 容器组件
容器组件负责数据获取,展示组件负责根据props显示信息
四.组件嵌套
组件化的核心思想应该是对组件进行拆分,拆分成一个个小的组件,再将这些组件组合嵌套在一起,最终形成我们的应用程序;
我们来分析一下下面代码的嵌套逻辑:
import React, Component from 'react';
function Header()
return <h2>Header</h2>
function Main()
return (
<div>
<Banner/>
<ProductList/>
</div>
)
function Banner()
return <div>Banner</div>
function ProductList()
return (
<ul>
<li>商品1</li>
<li>商品2</li>
<li>商品3</li>
<li>商品4</li>
<li>商品5</li>
</ul>
)
function Footer()
return <h2>Footer</h2>
export default class App extends Component
render()
return (
<div>
<Header/>
<Main/>
<Footer/>
</div>
)
如代码所示:
App组件是Header、Main、Footer组件的父组件;
Main组件是Banner、ProductList组件的父组件;
六. 组件通信
1) ⽗组件向⼦组件通信
传 props 的⽅式:是通过在父组件添加属性,在子组件通过this.props.xxx(类组件使用this.props.xxx;函数组件props.xxx)的形式传值。
思路:父组件向子组件传值,通过props,将父组件的state传递给了子组件。
import React, Component from 'react'
export default class Correspond extends Component
render()
return (
<div>
<Chindren name="小白"></Chindren>
</div>
)
2) ⼦组件向⽗组件通信
可以采用 props + 回调 的⽅式( 通过调用父组件传过来的回调函数)
思路:子组件通过调用父组件传递到子组件的方法向父组件传递消息的。父组件收到参数后将值赋给父组件的state。
import React, Component from 'react'
function Chindren(props)
return <div>Welcome, props.name!</div>
class Welcome extends Component
constructor(props)
super(props)
this.state =
msg: this.props.toChildren,
toParent = () =>
// 通过props属性获取父组件的getdata方法,并将this.state值传递过去
this.props.callback('早上好!') //子组件通过此触发父组件的回调方法
render()
return (
<div>
<button onClick=this.toParent> 副官向上级打招呼</button>
</div>
)
export default class Correspond extends Component
constructor(props)
super(props)
this.state =
name: '副官',
back: '',
//用于接收子组件的传值方法,参数为子组件传递过来的值
changeMsg = (val) =>
//把子组件传递过来的值赋给this.state中的属性
this.setState(
back: val,
)
render()
return (
<div>
/* 子组件向父组件传参数 */
<div>上级:Welcome, this.state.name!</div>
this.state.back && <div>副高:this.state.back</div>
/* 注意:子组件中props的回调函数名称 和 父组件传递添加的方法(如:callback) 需一致 */
<Welcome callback=this.changeMsg></Welcome>
</div>
)
3) 兄弟组件通信
redux实现和利用父组件(prop 一层一层传递)
思路:兄弟组件之间的传值,是通过父组件做的中转 ,流程为组件A – 传值 --> 父组件 – 传值 --> 组件B
import React, Component from 'react'
// Acls组件
class Acls extends Component
//按钮点击事件,向父组件传值
handleClick()
this.props.data('hello...React...')
render()
return (
<div>
<button onClick=this.handleClick.bind(this)>
Acls组件中获取数据
</button>
</div>
)
//Bcls组件
class Bcls extends React.Component
render()
return <div>在Bcls组件中展示数据:this.props.mess</div>
export default class Correspond extends Component
constructor(props)
super(props)
this.state =
mess: '',
//向子组件A 组件 提供的传值方法,参数为获取的子组件传过来的值
getDatas(data)
this.setState(
mess: data,
)
render()
return (
<div>
/* 兄弟组件传值 */
<Acls data=this.getDatas.bind(this)></Acls>
<Bcls mess=this.state.mess></Bcls>
</div>
)
4) 跨组件(如祖父与孙之间通信)
常用的方式有两种,逐层传值与跨层传值。
A. 逐层传值
先父子通信,然后再子“孙”通信,传递的层级变成祖父–>子–>“孙”,同理,通过props往下传,通过回调往上传。
B. 跨层传值
组件跨层级通信可使用Context 。
React官方文档对Context做出了解释:
在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递props。
一句话概括就是:跨级传值,状态共享。
这种模式下有两个角色,Provider和Consumer
Provider为外层组件,用来提供 数据;内部需要数据时用Consumer来读取
祖父——》孙
- 新建context.js文件(与父组件同级),默认值为一个对象
// 跨级传值 上下文
import React from 'react'
const MyContext = React.createContext( text: 'luck' )
export default MyContext
- 祖父组件编写:在祖父组件引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。
import React, Component from 'react'
import MyContext from './context'
import Grandson from './Grandson'
// 子组件
class Children extends React.Component
render()
return (
<div>
<Grandson></Grandson>
</div>
)
export default class Correspond2 extends Component
// 使用一个 Provider 来将当前的 value 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
render()
return (
<div
style=
backgroundColor: '#f7ba2a',
padding: '20px',
width: '500px',
margin: 'auto',
textAlign: 'center',
>
<p>父组件</p>
/* 对父组件:引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。 */
<MyContext.Provider value= text: '你好' >
<Children></Children>
</MyContext.Provider>
</div>
)
- 孙组件Grandson.js :同样需引入context,在组件内部添加static contextType = MyContext,此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即祖父组件传递value。
import React from 'react'
import MyContext from './context'
class Grandson extends React.Component
// 孙组件: 同样需引入context,在组件内部添加static contextType = MyContext,
// 此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即父组件传递value。
static contextType = MyContext
render()
return (
<div
style=
backgroundColor: '#13ce66',
padding: '10px',
width: '200px',
margin: 'auto',
marginTop: '20px',
>
<p>孙组件:</p>
<span style= color: 'blue' >this.context.text</span>
</div>
)
export default Grandson
通过this.context.text获取到传递的值
孙——》祖父传值 ,可以通过回调的方式
- 对祖父组件进行传值修改,在传过来的对象中添加一个属性,里面绑定祖父组件的方法value=text:‘good luck’,toParent:this.fromGranson
import React, Component from 'react'
import MyContext from './context'
import Grandson from './Grandson'
// 子组件
class Children extends React.Component
render()
return (
<div>
<Grandson></Grandson>
</div>
)
export default class Correspond2 extends Component
// 使用一个 Provider 来将当前的 value 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
constructor(props)
super(props)
this.state =
msg: '',
fromGranson = (val) =>
this.setState(
msg: val,
)
render()
return (
<div
style=
backgroundColor: '#f7ba2a',
padding: '20px',
width: '500px',
margin: 'auto',
textAlign: 'center',
>
<p>祖父组件</p>
<span style= color: 'red' >this.state.msg</span>
/* 对父组件:引入context,使用一个 Provider 来将当前的 value 传递给以下的组件树,value为传递的值。 */
<MyContext.Provider
value= text: '你好', toParent: this.fromGranson
>
<Children></Children>
</MyContext.Provider>
</div>
)
- 孙组件:添加一个按钮,绑定方法,执行函数回调
import React from 'react'
import MyContext from './context'
class Grandson extends React.Component
// 孙组件: 同样需引入context,在组件内部添加static contextType = MyContext,
// 此时将能通过this.context直接获取到上层距离最近的Provider传递的值,此时this.context = text:good luck,即父组件传递value。
static contextType = MyContext
toParent = () =>
this.context.toParent('孙组件向祖父组件传数据')
render()
return (
<div
style=
backgroundColor: '#13ce66',
padding: '10px',
width: '200px',
margin: 'auto',
marginTop: '20px',
>
<p>孙组件:</p>
<span style= color: 'blue' >this.context.text</span>
<div>
<button onClick=this.toParent>向上祖父组件</button>
</div>
</div>
)
export default Grandson
注意: 不管层级有多深,都可以使用context进行向下或向上传值。在下层组件中取的context中的字段需与value中传递字段保持一致。text与toParent
官网
Redux
数据共享状态
Redux基本语法
发布订阅模式event
发布订阅模式所有组件都可以。 event的方式比较灵活,不管是父子、跨级、还是同级,甚至毫无关联的组件,都可以使用此方式进行通信。
发布订阅模式
组件间通信需要引用一个类的实例,使用单例模式实现。
在 发布/订阅模式 有 发布者 和 订阅者,它们通过信道链接到一起。其主要包含三个对象:
订阅者:订阅一个或者多个信道消息的对象。
发布者:消息的发布者,往信道中投递消息的对象。
信道:每个信道都有一个名字,信道的实现细节对用户代码来说是隐藏的。
订阅 / 发布模型API设计思路
on(): 负责注册事件的监听(订阅),指定事件触发(发布)时的回调函数;
emit(): 负责触发事件,可以通过传参使其在触发的时候携带数据
off():负责监听器的删除(解除事件)
优缺点
在这种模式中,一个目标对象(被观察者)管理所有的依赖于它的对象(观察者),并且在它本身的状态发生变化的时候主动发出通知。
其主要包含两个对象:被观察者和观察者
优点:
跨组件进行通信
利于合作,对于某些操作,其他人不需要清楚具体的逻辑,只需要去发起就行。
缺点:
耦合问题:每个观察者必须和被观察对象绑定在一起,这引入了耦合
性能问题:在最基本的实现中观察对象必须同步地通知观察者。这可能会导致性能瓶颈。
案例
安装event
npm install event -save
- 新建event.js
import EventEmitter from ‘events’;
export default new EventEmitter(); - 另两个组件处于同层级
import React from 'react';
import GrandsonEven from './GrandsonEven';
import GrandsonOtherEven from './GrandsonOtherEven';
class Children extends React.Component
render()
return (
<div>
<GrandsonEven></GrandsonEven>
<GrandsonOtherEven></GrandsonOtherEven>
</div>
)
export default Children
- 在GrandsonEven组件里。导入event,在componentDidMount阶段添加监听on,事件名称与GrandsonOtherEven组件中emit一致
import React from 'react'
import event from './event'
class GrandsonEven extends React.Component
constructor(props)
super(props)
this.state =
msg: '',
getData = (params) =>
this.setState(
msg: params,
)
componentDidMount()
//监听eventMsg
event.on('eventMsg', this.getData)
render()
return (
<div
style=
backgroundColor: '#13ce66',
padding: '10px',
width: '200px',
margin: 'auto',
marginTop: '20px',
>
<p>组件一</p>
<div>组件二通过event传递值过来:this.state.msg</div>
</div>
)
export default G以上是关于Reac组件化以及组件通信的主要内容,如果未能解决你的问题,请参考以下文章