React的⼦传⽗
通过一个案例来具体说明
首先还是先创建一个子组件
class ChildCom extends React.Component{
constructor(props){
super(props)
this.state = {
msg:'hello'
}
}
render(){
return(
<div>
<button onClick={this.sendData}>传递给父</button>
<button onClick={()=>{this.props.setChildData("直接调用props")}}>传递给父</button>
{/* 让我们不用写这个sendData,这个可以代替下面定义的sendData方法 */}
</div>
)
}
sendData=()=>{
console.log(this.state.msg)
//将子元素传递给父元素,实际就是调用父元素传递进来的函数
this.props.setChildData(this.state.msg)
}
}
然后创建父组件
class ParentCom extends React.Component{
constructor(props){
super(props)
this.state ={
childData :null
}
}
render(){
return(
<div>
<h1>子元素传递给父元素的数据:{this.state.childData}</h1>
<ChildCom setChildData ={this.setChildData}></ChildCom>
</div>
)
}
setChildData=(data)=>{
this.setState({
childData:data
})
}
}
比vue是繁琐了一点…通过调用父元素的函数从而操作父元素
React组件通信与⽣命周期
需要组件之进行通信的几种情况
- 父组件向子组件通信
- 子组件向父组件通信
- 跨级组件通信
- 没有嵌套关系组件之间的通信
1.父组件向子组件通信
React数据流动是单向的,父组件向子组件通信也是最常见的;父组件通过props向子组件传递需要的信息
Child.jsx
import React from 'react';
import PropTypes from 'prop-types';
export default function Child({ name }) {
return <h1>Hello, {name}</h1>;
}
Child.propTypes = {
name: PropTypes.string.isRequired,
};
Parent.jsx
import React, { Component } from 'react';
import Child from './Child';
class Parent extends Component {
render() {
return (
<div>
<Child name="Sara" />
</div>
);
}
}
export default Parent;
2. 子组件向父组件通信
- 利用回调函数
- 利用自定义事件机制
回调函数
实现在子组件中点击隐藏组件按钮可以将自身隐藏的功能
List3.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class List3 extends Component {
static propTypes = {
hideConponent: PropTypes.func.isRequired,
}
render() {
return (
<div>
哈哈,我是List3
<button onClick={this.props.hideConponent}>隐藏List3组件</button>
</div>
);
}
}
export default List3;
App.jsx
import React, { Component } from 'react';
import List3 from './components/List3';
export default class App extends Component {
constructor(...args) {
super(...args);
this.state = {
isShowList3: false,
};
}
showConponent = () => {
this.setState({
isShowList3: true,
});
}
hideConponent = () => {
this.setState({
isShowList3: false,
});
}
render() {
return (
<div>
<button onClick={this.showConponent}>显示Lists组件</button>
{
this.state.isShowList3 ?
<List3 hideConponent={this.hideConponent} />
:
null
}
</div>
);
}
}
3. 跨级组件通信
- 层层组件传递props
例如A组件和B组件之间要进行通信,先找到A和B公共的父组件,A先向C组件通信,C组件通过props和B组件通信,此时C组件起的就是中间件的作用
- 使用context
context是一个全局变量,像是一个大容器,在任何地方都可以访问到,我们可以把要通信的信息放在context上,然后在其他组件中可以随意取到;
但是React官方不建议使用大量context,尽管他可以减少逐层传递,但是当组件结构复杂的时候,我们并不知道context是从哪里传过来的;而且context是一个全局变量,全局变量正是导致应用走向混乱的罪魁祸首.
使用context
下面例子中的组件关系: ListItem是List的子组件,List是app的子组件
ListItem.jsx
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class ListItem extends Component {
// 子组件声明自己要使用context
static contextTypes = {
color: PropTypes.string,
}
static propTypes = {
value: PropTypes.string,
}
render() {
const { value } = this.props;
return (
<li style={{ background: this.context.color }}>
<span>{value}</span>
</li>
);
}
}
export default ListItem;
List.jsx
import ListItem from './ListItem';
class List extends Component {
// 父组件声明自己支持context
static childContextTypes = {
color: PropTypes.string,
}
static propTypes = {
list: PropTypes.array,
}
// 提供一个函数,用来返回相应的context对象
getChildContext() {
return {
color: 'red',
};
}
render() {
const { list } = this.props;
return (
<div>
<ul>
{
list.map((entry, index) =>
<ListItem key={`list-${index}`} value={entry.text} />,
)
}
</ul>
</div>
);
}
}
export default List;
App.jsx
import React, { Component } from 'react';
import List from './components/List';
const list = [
{
text: '题目一',
},
{
text: '题目二',
},
];
export default class App extends Component {
render() {
return (
<div>
<List
list={list}
/>
</div>
);
}
}
4. 没有嵌套关系的组件通信
- 使用自定义事件机制
在componentDidMount事件中,如果组件挂载完成,再订阅事件;在组件卸载的时候,在componentWillUnmount事件中取消事件的订阅;
以常用的发布/订阅模式举例,借用Node.js Events模块的浏览器版实现
使用自定义事件的方式
下面例子中的组件关系: List1和List2没有任何嵌套关系,App是他们的父组件;
实现这样一个功能: 点击List2中的一个按钮,改变List1中的信息显示
首先需要项目中安装events 包:
npm install events --save
在src下新建一个util目录里面建一个events.js
import { EventEmitter } from 'events';
export default new EventEmitter();
list1.jsx
import React, { Component } from 'react';
import emitter from '../util/events';
class List extends Component {
constructor(props) {
super(props);
this.state = {
message: 'List1',
};
}
componentDidMount() {
// 组件装载完成以后声明一个自定义事件
this.eventEmitter = emitter.addListener('changeMessage', (message) => {
this.setState({
message,
});
});
}
componentWillUnmount() {
emitter.removeListener(this.eventEmitter);
}
render() {
return (
<div>
{this.state.message}
</div>
);
}
}
export default List;
APP.jsx
import React, { Component } from 'react';
import List1 from './components/List1';
import List2 from './components/List2';
export default class App extends Component {
render() {
return (
<div>
<List1 />
<List2 />
</div>
);
}
}
自定义事件是典型的发布订阅模式,通过向事件对象上添加监听器和触发事件来实现组件之间的通信
react生命周期
React v16废弃的三个生命周期函数
- componentWillMount : 组件初始化时调用,整个生命周期只调用一次,此时可以修改state。
- componentWillReceiveProps(nextProps):当props发生变化的时候调用
- componentWillUpdate :组件数据更新前调用,此时可以更改state。
React v16后为什么会废弃这三个生命周期函数?
由于React后期版本推出Fiber(异步渲染),在dom被挂载之前的阶段都可以被重新打断重来,导致componentWillMount、componentWillReceiveProps、componentWillUpdate在一次更新中可能会被执行多次
React v16 后新增的2个生命周期函数
- static getDerivedStateFromProps :
与componentDidUpdate配合使用,可以覆盖componentWillReceiveProps的所有用法。 - getSnapshotBeforeUpdate:与componentDidUpdate配合使用,可以覆盖componentWillUpdate的所有用法。
React v16的生命周期
-
挂载阶段
-
更新阶段
-
卸载阶段
挂载阶段
挂载阶段也可以理解为组件的初始化阶段,就是将我们的组件插入到DOM中,只会发生一次。
- constructor:组件构造函数,第一个被执行,我们通常在构造函数里初始化state对象或者给自定义方法绑定this。
constructor(props) {
super(props)
this.state = {
select,
height: 'atuo',
externalClass,
externalClassText
}
this.handleChange1 = this.handleChange1.bind(this)
this.handleChange2 = this.handleChange2.bind(this)
}
禁止在构造函数中调用setState,可以直接给state设置初始值
- static getDerivedStateFromProps(nextProps, prevState)
:这是一个静态方法,所以不能在这个函数里面使用this,这个函数有2个参数nextProps、prevState,分别指接收到的新参数和当前的state对象,这个函数返回一个对象用来更新当前的state对象,如果不需要更新可以返回null.当我们接收到新的属性想去修改我们的state,可以使用该函数
class ExampleComponent extends React.Component {
state = {
isScrollingDown: false,
lastRow: null
}
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.currentRow !== prevState.lastRow) {
return {
isScrollingDown:
nextProps.currentRow > prevState.lastRow,
lastRow: nextProps.currentRow
}
}
return null
}
}
- render:render函数是纯函数,只返回需要渲染的东⻄,不应该包含其它的业务逻辑,可以返回原⽣的DOM、React
组件、Fragment、Portals、字符串和数字、Boolean和null等内容。 - componentDidMount:组件装载之后调用,此时我们可以获取到DOM节点并操作,比如对canvas,svg的操作,服务器请求,订阅都可以写在这个里面,但是记得在componentWillUnmount中取消订阅。
componentDidMount() {
const { progressCanvas, progressSVG } = this
const canvas = progressCanvas.current
const ctx = canvas.getContext('2d')
canvas.width = canvas.getBoundingClientRect().width
canvas.height = canvas.getBoundingClientRect().height
const svg = progressSVG.current
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
rect.setAttribute('x', 0)
rect.setAttribute('y', 0)
rect.setAttribute('width', 0)
rect.setAttribute('height', svg.getBoundingClientRect().height)
rect.setAttribute('style', 'fill:red')
const animate = document.createElementNS('http://www.w3.org/2000/svg', 'animate')
animate.setAttribute('attributeName', 'width')
animate.setAttribute('from', 0)
animate.setAttribute('to', svg.getBoundingClientRect().width)
animate.setAttribute('begin', '0ms')
animate.setAttribute('dur', '1684ms')
animate.setAttribute('repeatCount', 'indefinite')
animate.setAttribute('calcMode', 'linear')
rect.appendChild(animate)
svg.appendChild(rect)
svg.pauseAnimations()
this.canvas = canvas
this.svg = svg
this.ctx = ctx
}
在componentDidMount中调用setState会触发一次额外的渲染,多调用一次render函数,但是用户对此没有感知,因为它时在浏览器刷新屏幕前执行的,但是我们应该在开发中避免它,因为它会带来一定的性能问题,我们应该在constructor中初始化我们的state对象,而不应该在componentDIdMount调用state方法。
更新阶段
更新阶段,当组件的props改变了,或组件内部调用了setState或者forceUpdate发生,会发生多次。
-
getDerivedStateFromProps:这个方法在更新阶段也会触发。记住无论我们接收到的新的属性,调用了setState还是forceUpdate,这个方法都会被触发。
-
houldComponentUpdate(nextProps,
nextState):有两个参数nextProps和nextState,表示
新的属性和变化之后的state,返回⼀个布尔值,true表示会触发重新渲染,false表示不会触发重新渲染,默认返回
true,我们通常利⽤此⽣命周期来优化React程序性能 。
注意当我们调用forceUpdate并不会触发该方法。因为默认时返回true,也就是只有接收到新的属性和调用了setState都会触发重新的渲染,这会带来一定的性能问题,所以我们需要将this.props和nextprops以及this.state和nextState进行比较来决定是否返回false,来减少重新渲染。
但是官方提倡我们使用PureComponent来减少重新渲染的次数而不是手动编写shouldComponentUpdate代码,具体该怎么选择,全凭开发者自己选择。 -
render:更新阶段也会触发,这里不再
-
getSnapshotBeforeUpdate(prevProps, prevState):这个⽅法在render之后,
componentDidUpdate之前调⽤,有两个参数prevProps和prevState,表示之前的属性和之前的state,这个函数有
⼀个返回值,会作为第三个参数传给componentDidUpdate,如果你不想要返回值,可以返回null,此⽣命周期必须
与componentDidUpdate搭配使⽤。
class ScrollingList extends React.Component {
constructor(props) {
super(props);
this.listRef = React.createRef();
}
getSnapshotBeforeUpdate(prevProps, prevState) {
// Are we adding new items to the list?
// Capture the scroll position so we can adjust scroll later.
if (prevProps.list.length < this.props.list.length) {
const list = this.listRef.current;
return list.scrollHeight - list.scrollTop;
}
return null;
}
componentDidUpdate(prevProps, prevState, snapshot) {
// If we have a snapshot value, we've just added new items.
// Adjust scroll so these new items don't push the old ones out of view.
// (snapshot here is the value returned from getSnapshotBeforeUpdate)
if (snapshot !== null) {
const list = this.listRef.current;
list.scrollTop = list.scrollHeight - snapshot;
}
}
render() {
return (
<div ref={this.listRef}>{/* ...contents... */}</div>
);
}
}
- componentDidUpdate(prevProps, prevState,
snapshot):该方法在getSnapshotBeforeUpdate方法之后被调用,有三个参数prevProps,prevState,snapshot,表示之前的props,之前的state,和snapshot。第三个参数是getSnapshotBeforeUpdate返回的。在这个函数里我们可以操作DOM,和发起服务器请求,还可以setState,但是注意一定要用if语句控制,否则会导致无限循环。
卸载阶段
- componentWillUnmount:当我们的组件被卸载或者销毁了就会调用,我们可以在这个函数里去清除一些定时器,取消网络请求,清理无效的DOM元素等垃圾清理工作。注意不要在这个函数里去调用setState,因为组件不会重新渲染了
受控组件
在HTML中,标签、、的值的改变通常是根据用户输入进行更新。在React中,可变状态通常保存在组件的状态属性中,并且只能使用 setState() 更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。
class NameForm extends React.Component {
constructor(props) {
super(props);
this.state = {name: ''};
this.handleNameChange = this.handleNameChange.bind(this);
}
handleNameChange(event) {
this.setState({ name: event.target.value });
};
render() {
return (
<div>
<input type="text" value={this.state.name} onChange={this.handleNameChange}/>
</div>
);
}
}
- ame开始是空字符串’’。
- 当键入a,并handleNameChange获取a和调用setState。然后,该输入被重新呈现为具有的值a。
- 当键入b,handleNameChange获取ab并设置该状态的值。现在再次重新渲染输入value=“ab”。
这也意味着表单组件可以立即响应输入更改; 例如:
- 就地反馈,如验证
- 禁用按钮,除非所有字段都有有效的数据
- 执行特定的输入格式,如信用卡号码

可见效果:
当注释this.setState({value: event.target.value}); 这行代码,文本框再次输入时,页面不会重新渲染,所产生效果即是文本框输入不了值,即文本框值的改变受到setState()方法的控制,在未执行时,不重新渲染组件。

被折叠的 条评论
为什么被折叠?



