目录
组件的定义:
- 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
- 为什么要用组件: 一个界面的功能更复杂
- 作用:复用编码, 简化项目编码, 提高运行效率
React内的组件分为容器组件和展示组件。
这里呢,需要你优先有一个React的项目,在项目内操作,创建项目可查看React框架入门-创建项目、JSX了解
组件概念
React组件分类
-
函数组件
-
不具备“状态、ref、周期函数”等内容,第一次渲染完毕后,无法基于组件内部的操作来控制其更新,因此称之为静态组件!
-
但是具备属性及插槽,父组件可以控制其重新渲染!
-
渲染流程简单,渲染速度较快!
-
基于FP(函数式编程)思想设计,提供更细粒度的逻辑组织和复用!
-
-
类组件
-
具备“状态、ref、周期函数、属性、插槽”等内容,可以灵活的控制组件更新,基于钩子函数也可灵活掌控不同阶段处理不同的事情!
-
渲染流程繁琐,渲染速度相对较慢!
-
基于OOP(面向对象编程)思想设计,更方便实现继承等!
-
React Hooks 组件,就是基于 React 中新提供的 Hook 函数,可以让函数组件动态化
!
创建组件
简单例子
在 项目src下新建 App.js文件(index.js与App.js同级),作为组件,进入组件后直接输入 rcc 会出现组件的简单框架并导入React及Component,
基本代码如下:
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
App组件
</div>
)
}
}
最后在index.js(入口文件)内引入是使用:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
ReactDOM.render(
<App />,
document.getElementById('root')
);
最后运行会出现
表示组件引入并运行正常!
组件的两种类型
第一种方式:类式组件(容器组件)
声明一个类继承于Component
import React from 'react'
class App extends React.Component{
render(){
return <h1>类式组件</h1>
}
}
//或者
import React, { Component } from 'react'
export default class App extends Component {}
注意:容器组件的使用条件:
- 必须要继承父类 extends React.Component
- 必须要有render,且render必须有返回值
第二种:函数式组件(展示组件)
基本原则:容器组件负责数据获取,展示组件赋值根据props展示信息
这种方式更加简洁明了
function App() {
return (
<div>
11
</div>
);
}
现在这两个写法的具体差异是这样,内容相同的情况下,实现的功能也是一致的。
二者的使用注意
如果你组件的内容内有状态state的变化,会影响到组件视图的变化。例如,组件内的值定时地在变化等必须将组件声明为class类型,因为在class内可以改变其状态声明其状态。
如果这个组件是展示型组件,就可以直接使用函数型组件。
当然,在组件使用时,一定要加上 export default 将组件导出,才可进行使用。
PureComponent
在15的版本出现的组件,纯组件PureComponent,定制了shouldComponentUpdate后的Component(浅比较),实现了一个预定义的比较方式,如果数据没有发生更改,就不会再进行组件的渲染。
因为这里是进行了一个浅比较操作,只比较第一层,所以对象内深层次的数据就无法比较。
例子:
import React from 'react'
class Comment extends React.PureComponent {
}
要使用PureComponent组件的特性应遵循以下几点(需要注意):
- 确保数据类型是值类型
- 如果是引用类型,保证地址不变(还是同一个对象,不是新的对象),同时不应当有深层次的数据变化
缺点:必须使用类的类型(class形式)
React.memo
React v16.6.0之后的版本可以使用一个新功能React.memo来实现React组件,让函数式组件也有了PureComponent的功能。
const joke = React.memo(() => {
<div>
{this.PaymentResponse.value || 'loading...'}
</div>
})
这个组件是拥有和纯函数功能的函数组件。
组件复合
组件复合而非继承
组件复合的原则:保证组件功能的单一性。
先举个例子(简单的复合组件):
import React, { Component } from 'react'
function Dialog(props){
return (
<div style={{border:`4px solid ${props.color || 'blue'}`}}>
{/* {props.children}等价于vue内的插槽 */}
{props.children}
{/* className="footer" 等价于vue内的具名插槽 */}
<div className="footer">{props.footer}</div>
</div>
)
}
function WelcomeDialog(){
const confirmBtn = <button onClick={()=>alert('react好!!!')}>确定</button>
return (
<Dialog color="green" footer={confirmBtn}>
<h1>欢迎光临</h1>
<p>感谢使用react</p>
</Dialog>
);
}
export default class Composition extends Component {
render() {
return (
<div>
<WelcomeDialog></WelcomeDialog>
</div>
)
}
}
ReactDOM.render(<Composition />,document.getElementById('root'));
说的是等价于插槽,react内没有插槽的概念。
高阶组件
提高组件复用率,首先想到的是抽离相同逻辑,在React内有了HOC(Higher-Order Components)的概念
高阶组件其实是个函数,高阶组件也是一个组件,但是他返回另一个组件,产生新的组件对属性进行包装,也可以重写部分生命周期
返回值:返回一个组件
简单来说就是高阶组件就是一个函数,传入一个组件会返回一个全新的组件。
先写一个最简单的高阶组件的例子:
Hoc.js (位于src下的)
// 定义高阶组件
import React, {Component} from 'react'
function showName(props) {
return (
<div>{props.stage} - {props.name}</div>
);
}
// 高阶组件
const withName = Comp => {
// 假设通过某种特殊手段来获取名称
return props => <Comp {...props} name="高阶组件"></Comp>
}
export default withName(showName)
index.js
import Hoc from './components/Hoc';
ReactDOM.render(<Hoc stage="React"/>,document.getElementById('root'));
上面的withName便是高阶组件,会传递给showName组件需要的东西
上述的代码便是简单的高阶组件,高阶组件甚至可以重写组件的生命周期。
这里重写生命周期的操作是在withName高阶组件内进行的操作。
const withName = Comp =>{
class NewComponent extends Component{
// 改写生命周期
componentDidMount () {
console.log('do something');
}
render(){
return <Comp {...this.props} name="高阶组件的使用介绍"/>
}
}
return NewComponent;
}
高阶组件的链式调用
高阶组件可以链式调用,如下,代码内高阶组件相互嵌套,从内到外执行。
import React, { Component } from 'react'
function ShowName(props){
return(
<div>{props.stage}-{props.name}</div>
)
}
//高阶组件,就是一个函数(扩充简单组件的能力)
const withName = Comp =>{
class NewComponent extends Component{
//改写生命周期的能力
componentDidMount () {
console.log('do something');
}
render(){
return <Comp {...this.props} name="高阶组件"/>
}
}
return NewComponent;
}
//高阶组件
const withLog = Comp =>{
console.log(Comp.name + '渲染了');
return props => <Comp {...props}/>
}
//高阶组件链式调用,由内向外执行(可读性较差)
export default withLog(withName(ShowName));
等于这个组件可以调用另一个高阶组件。
高阶组件的装饰器写法
从上方代码可以看到,链式写法很难以理解,而且写法很不便,ES7内有一个优秀的语法-装饰器,专门处理这种问题。
配置操作如下:
1.要给高阶组件使用装饰器,首先需要安装:
npm install --save-dev babel-plugin-transform-decorators-legacy
在安装完成之后就需要进行配置了,需要在和package.js文件平级的位置添加 config-overrides.js 文件,
2.配置内容如下:
const {injectBabelPlugin} = require('react-app-rewired')
module.exports = function override(config,env){
//antd的按需加载的写法
config = injectBabelPlugin([
'import',
{libraryName:'antd',libraryDirectory:'es',style:'css'}
],config);
//添加装饰器的能力
config = injectBabelPlugin(
["@babel/plugin-proposal-decorators",{ legacy :true}],
config
);
return config;
}
代码内的react-app-rewired部分具体安转在Ant Design of React的安装、按需加载的按需加载内,安装即可。
在配置完成后,需要重启才能生效。
3.代码:
import React, { Component } from 'react'
//高阶组件,就是一个函数(扩充简单组件的能力)
const withName = Comp =>{
class NewComponent extends Component{
//改写生命周期的能力
componentDidMount () {
console.log('do something');
}
render(){
return <Comp {...this.props} name="高阶组件"/>
}
}
return NewComponent;
}
//高阶组件
const withLog = Comp =>{
console.log(Comp.name + '渲染了');
return props => <Comp {...props}/>
}
@withLog
@withName
class ShowName extends Component{
render(){
return(
<div>
{this.props.stage}-{this.props.name}
</div>
)
}
}
//高阶组件链式调用,由内向外执行(可读性较差),使用装饰器无需链式操作
export default ShowName;
注意:
- 使用装饰器的组件必须是class组件
- 添加装饰器时,必须要在被装饰的组件前被定义好,否则会报错
- 添加装饰器时,class组件内不能有导出export
组件通信--上下文
vuejs的provide&inject模式的来源--context
这种模式下有两个角色,一个是Provider,一个是Consumerr,Provider在外部的组件内部需要数据的就用Consumer来读取。
import React, { Component } from 'react'
//组件通信--上下文
//1.创建上下文
const Context = React.createContext();
const store = {
name :"组件上下文",
sayHi(){
console.log(this.name);
}
}
export default class ContextSample extends Component {
render() {
return <Context.Provider value={store}>
<div>
{/* 获取数据 */}
<Context.Consumer>
{/* 必须嵌套一个函数 */}
{value => <div onClick={()=>value.sayHi()}>{value.name}</div>}
</Context.Consumer>
</div>
</Context.Provider>
}
}
Context.Provider标签可以使用点语法进行值的传递,这里标签内一定要有value这个属性进行传值。
获取数据的话,需要使用Context.Consumer将其包裹起来,这里必须内嵌一个函数,拿出Context.Provider传递过来的value值,获取到值后进行渲染输出。
其实有简化的一种写法,可以根据装饰器来进行简化:
import React, { Component } from 'react'
const Context = React.createContext();
const store = {
name :"组件上下文",
sayHi(){
console.log(this.name);
}
}
//简化写法
//高阶组件的封装
const withProvider = Comp => props =>{
<Context.Provider value={store}>
<Comp>{...props}</Comp>
</Context.Provider>
}
const withConsumer = Comp => props =>{
<Context.Consumer>
{/* 必须嵌套一个函数 */}
{value => <Comp {...props} value={value}/>}
</Context.Consumer>
}
@withConsumer
class Inner extends Component{
render(){
return(
<div>{this.props.value.name}</div>
)
}
}
@withProvider
class ContextSample extends Component {
//导出和装饰器不能同时使用,只在ContextSample套一层withProvider
render() {
<div><Inner></Inner></div>
}
}
export default ContextSample;
这种写法是将其分开完成,由外部组件 ContextSample 提供数据store,内部组件进行获取数据,分别对外部组件和内部组件使用withProvider 和withConsumer装饰器,达到简化的作用。
这里简化后的结果,与简化前的一致。
注意:Provider必须和Consumer配套使用