前端开发项目的JavaScript库之React详解--react组件之状态与通信(二)

92 阅读7分钟

作者名:木木

六、组件之状态与通信

在React.js中,定义一个组件的最简单的方式就是 函数

1> 函数式组件:

function Kaikeba() {
        return (
            <div>
                <h2>开课吧!</h2>
            </div>
        );
    }
    ReactDOM.render(
        <Kaikeba />,
        document.getElementById('app')
    );

// 函数的名称就是组件的名称,必须是首字母大写
// 函数的返回值就是组件要渲染的内容

2> 类组件:

 class Miaov extends React.Component {
        render() {
            return (
                <div>
                    <h2>妙味!</h2>
                </div>
            );
        }
    }

// 组件类必须继承 React.Component
// 组件类必须有 render方法
// render 方法的返回值就是组件要渲染的内容
// 类的名称(组件的名称),也必须是首字母大写

3> 组件复用 - 数据传入

使用组件的时候通过 标签属性 -property 的方式传入数据,在组件内部通过构造函数参数(如果是函数组件,则通过函数参数)来接收传入的数据

<组件名称 属性名称="值" />
    // 使用表达式
<组件名称 属性名称={表达式} />

   ReactDOM.render(
        <FriendList datas={datas} />,
        document.getElementById('app')
   );

4> 接收参数 -props

  • 函数式组件:通过函数的第一个参数来接收;
  • 类式组件:通过类的 props 属性接收;

无论是函数式组件还是类式组件,都会把传入的参数封装成一个对象

<组件名称 属性名称1="值1" 属性名称二={表达式二} />

    // 函数式组件:
    // 函数创建的组件是无状态组件,没有state,没有生命周期方法,它是一种只负责展示的纯组件
    function 组件名称(参数) {
        // 参数的结构为:
        参数 = {
            属性名称1"值1",
            属性名称二: 表达式二的值
        }
    
        return <div>组件结构</div>
    }
 // 类式组件
    class 组件名称 extends React.Component {
        constructor(参数) {
            super(参数);

            this.props = {
                属性名称1"值1",
                属性名称2: 表达式二的值
            }
        }
    
        render() {
            return <div>组件结构</div>
        }
    }

// 在类式组件中,需要注意:
// 1.当子类重写 constructor,则必须调用父类 super
// 2.把接收到的参数传入父类构造函数,父类构造函数中会创建一个对象属性:props 类存储传入的参数数据

5> 通过参数动态渲染组件结构

class FriendList extends React.Component {
        constructor(props) {
            super(props);
        }
    
        render() {
            let {datas} = this.props;
            return (
            <div className="friend-list">
                {Object.keys(datas).map((key, index) => (
                    <div className="friend-group" key={key}>
                        <dt>{datas[key].title}</dt>
                        {datas[key].list.map((list, index) => (
                            <dd key={index}>{list}</dd>
                        )}
                    </div>
                ))}
            </div>
            );
        }
    }

6> 子组件提取

组件与类一样,是一个不断提取的过程,当我们发现某个部分可复用或者结构复杂的时候,我们可以对它再次进行提取

class FriendGroup extends React.Component {
            constructor(props) {
                super(props);
            }
    
            render() {
                let {data} = this.props;
                return (
                    <div className="friend-group">
                        <dt>{data.title}</dt>
                        {data.list.map((list, index) => {
                            <dd key={index}>{list}</dd>
                        })}
                    </div>
                );
            }
    }

    class FriendList extends React.Component {
            constructor(props) {
                super(props);
            }
    
            render() {
                let {datas} = this.props;
                return (
                    <div className="friend-list">
                        {Object.keys(datas).map((key, index) => (
                                <FriendGroup data={datas[key]} key={key} />
                        ))}
                    </div>
                );
            }
    }

7> 组件状态

  • 组件状态是什么? 状态被用来存储组件在某段时间内状态改变的信息。用户事件或系统事件会导致一些经典的状态改变。(比如:对用户输入的回应、服务器的请求、生命周期函数)

  • 组件 state 工作是这样的:先给组件设置一个默认状态,再获取当前状态,最后更新这个状态

  • 修改 state 的正确姿势: 不能直接修改state 在React中,直接修改state并不会触发render函数,所以下面的写法是错误的。

this.state.title = 'React';   // 错误
    
// 组件的State只能通过setState()方式进行修改。例如:

this.setState({title'React'});     // 正确
  • setState方法由父类React.Component提供,当该方法被调用时,组件的state会被更新,同时会被重新调用组件的render方法对组件进行渲染。
  • 组件构造函数是唯一可以对 state 直接赋值(初始化)的位置

8> 绑定事件

class FriendGroup extends React.Component {
        constructor(props) {
            super(props);
                this.state = {
                    expandedfalse
                };
                this.expand = this.click.expand();
        }
    
        expand() {
            this.setState({
                expanded: !this.state.expanded
            });
        }
    
        render() {
            let {data} = this.props;
            let {expanded} = this.state;
            return (
                <div className={["friend-group",expanded && "expanded"].join(' ')}>
                    <dt onClick={this.expand}>{data.title}</dt>
                    {data.list.map((list, index) => {
                        <dd key={index}>{list}</dd>
                    })}
                </div>
            );
        }
    }
React.js的事件绑定需要注意:
  • 事件名称是驼峰命名的
  • 事件绑定函数的this指向
  • 通过bind改变this指向,为了能够在方法中调用对象的属性和其他方法, 我们需要把this指向组件
  • 通过箭头函数处理
<dt onClick={e=>{this.expand}}>{data.title}</dt>

如上代码: 事件绑定函数的第一个参数是事件对象 因为this指向了组件对象,那么获取当前事件触发元素,可以通过e.target来获取

9> 获取原生DOM对象 有的时候我们也是需要操作原生DOM对象的,除了可以通过事件源来获取,还可以通过ref的方式来获取

class MyComponent extends React.Component {
        render() {
            return (
                <div>
                    <input ref={el=>this.el=el} />
                    <button onClick={this.todo.bind(this)}>Button</button>
                </div>
            );
        }
        todo() {
            console.log(this.el.value);
        }
    }

10> 更新异步 出于性能考虑,setState方法的修改并不是立即生效的

 // this.state.val = 0
    this.setState({
        valthis.state.val + 1
    });
    // this.state.val 的值并不是 1
    console.log(this.state.val);

11> 更新合并 React会把多个setState合并成一个调用

// this.state.val = 0
    this.setState({
        valthis.state.val + 1
    });
    this.setState({ // 因为异步的问题,this.state.val 的值在这里还是0
        valthis.state.val + 1
    });

12> props与state的区别

  • state的主要作用是用于 组件保存、控制、修改自己的可变状态,在组件内部进行初始化,也可以在组件内部进行修改,但是组件外部不能修改组件的state
  • props的主要作用是让使用该组件的父组件可以传入参数来配置该组件,它是外部传进来的配置参数,组件内部无法控制也无法修改
  • state和props都可以决定组件的外观和显示状态。通常,props作为不变数据或者初始化数据传递给组件,可变状态使用state
  • 能使用props就不要使用state

13> 无状态组件

  • 没有状态的组件,我们成为 无状态组件。
  • 因为状态会带来复杂性,所以,通常我们推荐使用无状态组件。
  • 函数式组件没有state,所以通常我们编写使用函数式组件来编写无状态组件

14> 数据流

  • 在React.js中,数据是从 上自下流动(传递) 的,也就是一个父组件可以把它的state/props通过props传递给它的 子组件 ,但是 子组件不能修改props。
  • React.js是单向数据流,如果子组件需要修改父组件状态(数据),是通过 回调函数 方式来完成的。

15> React组件通信方式汇总:

需要组件之前进行通信的几种情况:

  • 父组件向子组件通信
  • 子组件向父组件通信
  • 跨级组件通信
  • 没有嵌套关系组件之间的通信
1.父组件向子组件通信

父组件通过向子组件传递props,子组件得到props后进行相应的处理

2.子组件向父组件通信
3.跨级组件通信

使用context context是一个全局变量,像是一个大容器,在任何地方都可以访问到,我们可以把要通信的信息放在context上,然后在其他组件中可以随意取到

使用context也很简单,需满足:

  • 1、上级组件要声明自己支持context,提供context中属性的PropTypes,并提供一个函数来返回相应的context对象
  • 2、子组件要声明自己主要使用context,并提供其需要使用的context属性的PropTypes
  • 3、父组件需提供一个getChildContext函数,以返回一个厨师的context对象 如果组件中使用构造函数(constructor),还需要在构造函数中传入第二个参数context,并在super调用父类构造函数时传入context,否则会造成组建中无法使用context。

下面例子中的组件关系:ListItem是List的子组件,List是app的子组件

4.没有嵌套关系的组件通信(兄弟组件)

使用自定义事件机制

实现这样一个功能: 点击List2中的一个按钮,改变List1中的信息显示(List1和List2没有任何嵌套关系,App是他们的父组件)

  • 首先需要项目中安装events包:
  npm install events --save
  • 在src下新建一个util目录里面见一个events.js
  import { EvenetEmitter } from 'events';
  export default new EventEmitter();
  • list1.js
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;
  • List2.js
 import React, { Component } from 'react';
    import emitter from '../util/events';

    class List2 extends Component {
        handleClick = (message) => {
            emitter.emit('changeMessage', message);
        };
        render() {
            return (
                <div>
                    <button onClick={this.handleClick.bind(this, 'List2')}>点击我改变List1组件中显示信息</button>
                </div>
            );
        }
    }
  • APP.js
 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>
            );
        }
    }
  • 自定义事件是典型的发布订阅模式,通过向事件对象上添加监听器和触发事件来实现组件之间的通信