react(第二天)

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()方法的控制,在未执行时,不重新渲染组件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值