# 一切皆组件
在 React 世界里,一切皆组件, React 项目全部起源于组件。组件可以分为两类,一类是类( Class )组件,一类是函数( Function )组件。
# 什么是 React 组件
代码语言:javascript复制/** 类 */
class textClass {
sayHello = () => {
console.log("Hello");
}
}
/** 类组件 */
class Index extends React.Component {
state = {
message: "Hello World"
};
sayHello = () => {
this.setState({
message: "Hello Cell"
});
};
render () {
return <div
style={{marginTop: '30px'}}
onClick={this.sayHello}
>
{this.state.message}
</div>;
}
}
/** 函数 */
function textFun() {
return 'Hello World';
}
/** 函数组件 */
function FunComponent() {
const [message, setMessage] = useState('Hello World');
const sayHello = () => {
setMessage('Hello Cell');
};
return <div
style={{marginTop: '30px'}}
onClick={sayHello}
>
{message}
</div>;
}
组件本质上就是类和函数,但是与常规的类和函数不同的是,组件承载了渲染视图的 UI 和更新视图的 setState 、 useState 等方法。React 在底层逻辑上会像正常实例化类和正常执行函数那样处理的组件。
函数与类上的特性在 React 组件上同样具有,比如原型链,继承,静态属性等,所以不要把 React 组件和类与函数独立开来。
React 对组件的处理流程:
对于类组件的执行,是在 react-reconciler/src/ReactFiberClassComponent.js
中
function constructClassInstance(
workInProgress, // 当前正在工作的 fiber 对象
ctor, // 我们的类组件
props // props
){
/* 实例化组件,得到组件实例 instance */
const instance = new ctor(props, context)
}
对于函数组件的执行,是在 react-reconciler/src/ReactFiberHooks.js
中
function renderWithHooks(
current, // 当前函数组件对应的 `fiber`, 初始化
workInProgress, // 当前正在工作的 fiber 对象
Component, // 我们函数组件
props, // 函数组件第一个参数 props
secondArg, // 函数组件其他参数
nextRenderExpirationTime, //下次渲染过期时间
){
/* 执行我们的函数组件,得到 return 返回的 React.element对象 */
let children = Component(props, secondArg);
}
# 类组件和函数组件
# Class 类组件
# 类组件的定义
在 class
组件中,除了继承 React.Component
,底层还加入了 updater
对象,组件中调用的 setState
和 forceUpdate
本质上是调用了 updater
对象上的 enqueueSetState
和 enqueueForceUpdate
方法。
React 底层定义类组件 react/src/ReactBaseClasses.js
:
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
// renderer.
this.updater = updater || ReactNoopUpdateQueue;
}
// setState
Component.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// forceUpdate
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
类组件执行构造函数过程中会在实例上绑定 props
和 context
,初始化置空 refs
属性,原型链上绑定setState
、forceUpdate
方法。对于 updater
,React 在实例化类组件之后会单独绑定 update
对象。
Q:如果没有在 constructor
的 super
函数中传递 props
,那么接下来 constructor
执行上下文中就获取不到 props
,为什么?
A: 绑定 props
是在父类 Component
构造函数中,执行 super
等于执行 Component
函数,此时 props
没有作为第一个参数传给 super()
,在 Component
中就会找不到 props
参数,从而变成 undefined
,在接下来 constructor
代码中打印 props
为 undefined
。
类组件各个部分的功能:
代码语言:javascript复制class Index extends React.Component{
constructor(...arg){
super(...arg) /* 执行 react 底层 Component 函数 */
}
state = {} /* state */
static number = 1 /* 内置静态属性 */
handleClick= () => console.log(111) /* 方法: 箭头函数方法直接绑定在this实例上 */
componentDidMount(){ /* 生命周期 */
console.log(Index.number,Index.number1) // 打印 1 , 2
}
render(){ /* 渲染函数 */
return <div style={{ marginTop:'50px' }} onClick={ this.handerClick } >hello,React!</div>
}
}
Index.number1 = 2 /* 外置静态属性 */
Index.prototype.handleClick = ()=> console.log(222) /* 方法: 绑定在 Index 原型链的 方法*/
# 函数组件
React V16.8 hooks 问世以来,对函数组件的功能加以强化,可以在 function
组件中,做类组件一切能做的事情,甚至完全取缔类组件。
function Index() {
console.log(Index.number); /** 打印 1 */
const [message, setMessage] = useState('Hello World');
return <div
onClick={() => setMessage('Hello Cell')}
>
{message}
</div>;
}
Index.number = 1; /** 静态属性 */
不要试图给函数组件 prototype
绑定属性或方法,即使绑定了也没有用,因为 React 对函数组件的调用,是采用直接执行函数的方式,而不是 new
的方式。
函数组件和类组件本质的区别是什么?对于类组件来说,底层只需要实例化一次,实例中保存了组件的 state
等状态。对于每一次更新只需要调用 render
方法以及对应的生命周期就可以了。但是在函数组件中,每一次更新都是一次新的函数执行,一次函数组件的更新,里面的变量会重新声明。
为了能让函数组件可以保存一些状态,执行一些副作用钩子,React Hooks 应运而生,它可以帮助记录 React 中组件的状态,处理一些额外的副作用。
# 组件通信方式
# props 和 callback
props
和 callback
可以作为 React 组件最基本的通信方式,父组件可以通过 props
将信息传递给子组件,子组件可以通过执行 props
中的回调函数 callback
来触发父组件的方法,实现父与子的消息通讯。
父组件 -> 通过自身 state 改变,重新渲染,传递 props -> 通知子组件
子组件 -> 通过调用父组件 props 方法 -> 通知父组件
代码语言:javascript复制/** 子组件 */
function child(props) {
const { parentSay, sayToParent } = props;
return <div className='child'>
child
<div>parent say: {parentSay} </div>
<input placeholder='say to parent' onChange={(e) => sayToParent(e.target.value)} />
</div>;
}
/** 父组件 */
function parent() {
const [ childSay, setChildSay ] = useState('');
const [ parentSay, setParentSay ] = useState('');
return <div>
parent
<div>child say: {childSay}</div>
<input placeholder='say to child' onChange={(e) => setParentSay(e.target.value)} />
<Child parentSay={parentSay} sayToParent={setChildSay} />
</div>;
}
# ref
# React-redux 或 React-mobx
# context 上下文
# Event Bus 事件总线
可以利用 eventBus
也可以实现组件通信,但是在 React 中并不提倡用这种方式。
import { BusService } from './eventBus';
/** 子组件 */
function Child() {
const [ parentSay, setParentSay ] = useState('');
React.useEffect(() => {
BusService.on('parentSay', (v) => {
setParentSay(v);
});
return () => {
BusService.off('parentSay');
};
}, []);
return <div className='child'>
child
<div>parent say: {parentSay} </div>
<input placeholder='say to parent' onChange={(e) => BusService.emit('childSay', e.target.value)} />
</div>;
}
/** 父组件 */
function Parent() {
const [ childSay, setChildSay ] = useState('');
React.useEffect(() => {
BusService.on('childSay', (v) => {
setChildSay(v);
});
return () => {
BusService.off('childSay');
};
}, []);
return <div>
parent
<div>child say: {childSay}</div>
<input placeholder='say to child' onChange={(e) => BusService.emit('parentSay', e.target.value)} />
<Child />
</div>;
}
eventBus
不仅达到了和使用 props
同样的效果,还能跨层级,不会受到 React 父子组件层级的影响。
但是很多地方还是不推荐使用,因为其有一些缺点:
- 需要手动绑定和解绑事件,容易出错
- 对于小型项目还好,对于中大型项目,这种方式的组件通信会造成牵一发动全身的影响,后期难以维护,并且组件之间的状态也是未知的
- 在一定程度上违背了 React 的单向数据流的设计思想
# 组件强化方式
# 类组件继承
因为 React 中类组件,有良好的继承属性,所以可以针对一些基础组件,首先实现一部分基础功能,再针对项目要求进行有方向的改造、强化、添加额外功能。
代码语言:javascript复制class Person extends React.Component {
constructor(props) {
super(props);
console.log('Person constructor');
}
componentDidMount() {
console.log('Person componentDidMount');
}
eat() {}
sleep() {}
render() {
return <div>Person</div>;
}
}
class Developer extends Person {
constructor(props) {
super(props);
console.log('Developer constructor');
}
componentDidMount() {
console.log('Developer componentDidMount');
console.log(this);
}
coding() {}
render() {
return <div>
{super.render()}
Also Developer
</div>;
}
}
export default Developer;
继承增强的优势:
- 可以控制父类
render()
,还可以添加一些其他的渲染内容 - 可以共享父类方法,还可以添加额外的方法和属性
需要注意的地方:
state
和 生命周期会被继承后的组件修改,如Person
中的componentDidMount
会被Developer
中的componentDidMount
覆盖