React组件核心

文章目录

  • 前言
  • 一、Props 的只读性
  • 二、State 和 setState
    • 正确地使用 State
    • props和state的区别
  • 三、事件处理
    • 函数组件当中使用:
    • 类式组件当中使用:
  • 四、子传父
  • 五、组件通信(跨组件)
    • 为什么使用Context?
    • Context.Provider
    • Context.Consumer
  • 六 非受控组件与受控组件
    • 非受控组件
    • 受控组件
  • 总结


前言

本文介绍了一下React中的 props&state、事件处理、子传父、组件通信以及非受控组件与受控组件,希望对你有所帮助。


一、Props 的只读性

props 是父组件传递过来的参数
组件无论是使用 函数声明 还是通过 class 声明,都决不能修改自身的 props。
React 非常灵活,但它也有一个严格的规则: 所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

因此我们出现了一个新的概念 state,那么这时大家就会疑惑这个state是干什么的呢?

二、State 和 setState

state 是组件自身状态,在不违反上述规则的情况下,state 允许 React 组件随用户操作、网络响应或者其他变化而动态更改输出内容。

  • setState(updater, [callback])
    • updater: 更新数据 FUNCTION/OBJECT
    • callback: 更新成功后的回调 FUNCTION
    • 异步:react通常会集齐一批需要更新的组件,然后一次性更新来保证渲染的性能
    • 浅合并 Object.assign()

在这里我们封装一个可复用的Clock 组件

function Clock(props) {
  return (
    <div>
      <h1>Hello React</h1>
      <h2>{props.date.toLocaleTimeString()}.</h2>
    </div>
  );
}

function tick() {
  ReactDOM.render(
    <Clock date={new Date()} />,
    document.getElementById('root')
  );
}

setInterval(tick, 1000);

上述代码中忽略了一个关键的技术细节:Clock 组件需要设置一个计时器,并且需要每秒更新,我们希望只编写一次代码,便可以让 Clock 组件自我更新,这里我们需要在 Clock 组件中添加 state来实现这个功能。

State 与 props 类似,但是 state 是私有的,并且完全受控于当前组件。

将函数组件转换成 class 组件
通过以下五步将 Clock 的函数组件转成 class 组件:

  1. 创建一个同名的 ES6 class,并且继承于React.Component
  2. 添加一个空的 render() 方法。
  3. 将函数体移动到 render()方法之中。
  4. render()方法中使用this.props 替换props
  5. 删除剩余的空函数声明。

向 class 组件中添加局部的 state

class Clock extends React.Component {
  constructor(props) {
    super(props);
    this.state = {date: new Date()};
  }

  render() {
    return (
      <div>
        <h1>Hello React</h1>
        <h2>{this.state.date.toLocaleTimeString()}.</h2>
      </div>
    );
  }
}

ReactDOM.render(
  <Clock />,
  document.getElementById('root')
);

正确地使用 State

应该使用 setState()

this.setState({data: 'Hello'});

构造函数是唯一可以给 this.state 赋值的地方

props和state的区别

  1. state是自己的私有状态,state是可以改变的,一般存在于父组件或者自身需要即时获取用户状态的情况下使用
  2. props我们只会去拿来用,不会去改变它,一般存在于子组件,父组件的state数据变化传递给子组件作为props来更新视图

三、事件处理

React 元素的事件处理和 DOM 元素的很相似,但是有一点语法上的不同:

  • React 事件的命名采用小驼峰式(camelCase),而不是纯小写。
  • 使用 JSX 语法时你需要传入一个函数作为事件处理函数,而不是一个字符串。

例如:

<button onClick={activateLasers}>
  点我
button>

在 React 中另一个不同点是你不能通过返回 false 的方式阻止默认行为。你必须显式的使用 preventDefault :

函数组件当中使用:

function ActionLink() {
  function handleClick(e) {
    e.preventDefault();
    console.log('我被点击了');
  }
  return (
    <a href="#" onClick={handleClick}>
      点我
    </a>
  );
}

使用 React 时,你一般不需要使用 addEventListener 为已创建的 DOM 元素添加监听器。事实上,你只需要在该元素初始渲染的时候添加监听器即可

类式组件当中使用:

class Toggle extends React.Component {
  state = {
  	flag: true
  };
  render() {
  	let {flag} = this.state
    return (
      <button onClick={
      	()=>{
			this.setState({
				flag:false
			})
		}
      }>
        {flag ? 'ON' : 'OFF'}
      </button>
    );
  }
}

ReactDOM.render(
  <Toggle />,
  document.getElementById('root')
);

四、子传父

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

  • 父级向子级通信 把数据添加子组件的属性中,然后子组件中从props属性中,获取父级传递过来的数据

  • 子级向父级通信 在父级中定义相关的数据操作方法(或其他回调), 把该方法传递给子级,在子级中调用该方法父级传递消息

代码如下(示例):

  1. 定义一个父组件Parent.js
export default class Parent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '我是父组件',
            msg: '父组件传值给子组件'
        }
    }

    render() {
        return (
            <div>
                <h2>{ this.state.name }</h2>
            </div>
        )
    }
}

  1. 定义一个子组件Children.js
import React, { Component } from 'react'
class Child extends Component{
    constructor(props){
        super(props)
        this.state = {
            data:"子组件数据"
        }
    }
    render(){
        return(
            <div>
                <button onClick={this.trans.bind(this,this.state.data)}>确定</button>
            </div>
        )
    }

    //点击子组件时,定义一个方法,调用父组件传过来的方法,把子组件数据传入到这个方法里
    trans(data){
        this.props.content(data)
    }
}
export default Child;
  1. 在App.js引入父组件
import React, { Component } from 'react';
import Parent from './Parent'
class App extends Component () {
  return (
      <div>
          <Parent/>
      </div>
  );
}
export default App;
  1. 父组件Parent引入子组件Children
import React, {Component} from 'react'
import Children from './Children'

export default class Parent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '我是父组件',
            msg: '父组件传值给子组件'
        }
    }

    render() {
    	let {name,age} = this.state
        return (
            <div>
                <h2>{ name }</h2>
                <hr />
                <Children/>
            </div>
        )
    }
}

子组件Children传值(msg)给父组件Parent

子组件传值给父组件的步骤:

  • 父组件在调用子组件时,传入一整个组件给子组件
  • 父组件中定义一个方法getChildrenMsg(resulet, msg),用来获取子组件传来的值以及执行其他操作
  • 子组件在通过this.props来获取到一整个组件this.props.parent或者this.props[parent]
  • 子组件调用父组件步骤2里定义的方法,通过bind绑定传值

父组件

import React, {Component} from 'react'
import Children from './Children'

export default class Parent extends Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '我是父组件',
            msg: '父组件传值给子组件',
            childrenMsg: ''
        }
    }

    getChildrenMsg = (result, msg) => {
        // console.log(result, msg)
        // result就是子组件bind的第一个参数this,msg是第二个参数
        this.setState({
            childrenMsg: msg
        })
    }

    render() {
        return (
            <div>
                <h2>{ this.state.name }</h2>
                <h3>子组件传来的值为:{ this.state.childrenMsg }</h3>
                <hr/>
                <Children parent={ this } />
            </div>
        )
    }
}

子组件

import React, {Component} from 'react'

export default class Children extends Component {
    constructor(props) {
        super(props)
        this.state = {
            name: '我是子组件',
            msg: '子组件传值给父组件'
        }
    }

    toParent = () => {
        // console.log(this.props.parent.getChildrenMsg.bind(this, this.state.msg))
        this.props.parent.getChildrenMsg(this, this.state.msg)
    }

    render() {
        return (
            <div>
                <h2>{ this.state.name }</h2>
                <button onClick={ this.toParent }>子组件传入给父组件</button>
            </div>
        )
    }
}

五、组件通信(跨组件)

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法

为什么使用Context?

在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些
类型的属性而言是极其繁琐的(例如:地区偏好,UI 主题),这些属性是应用程序中许多组件都需要的。
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props

代码如下(示例):

  1. 声明一个content.js组件,引入Content
import { createContext } from "react";
let context = createContext();
let { Consumer, Provider } = context;
export default context;
export { Consumer, Provider }

注意:Consumer & Provider 是两个标签

  1. 声明一个复用的data.js组件,存储数据
let datas = {
  family: {
    title: '家人',
    list: [
      { name: '爸爸' },
      { name: '妈妈' }
    ]
  },
  friend: {
    title: '朋友',
    list: [
      { name: '张三' },
      { name: '李四' },
      { name: '王五' },
      { name: '赵六' }
    ]
  },
  customer: {
    title: '客户',
    list: [
      { name: '阿里' },
      { name: '腾讯' },
      { name: '头条' }
    ]
  }
};
export default datas;
  1. 声明一个dl.js组件,遍历数据,接收另一个组件发送的数据
import React, { Component } from "react";
import { Consumer } from "./context";
export default class Dl extends Component {

  render() {
    // console.log(this.props);
    let { title, list } = this.props.value;
    let { isOpen, changeOpen, name } = this.props;
    return (
      <div className={"friend-group" + (name === isOpen ? ' expanded' : '')}>
        <dt onClick={
          () => {
            changeOpen(name)
          }
        }>{title}</dt>
        <p><Consumer>
          {value => value.info}
        </Consumer></p>
        {
          list.map((item, index) => {
            return <dd key={index}>{item.name}</dd>
          })
        }

      </div >
    )
  }
}
  1. 声明一个FriendList.js组件,添加点击效果
import React, { Component } from "react";
import "./FriendList.css";
import data from './data';
import Dl from './dl';
export default class FriendList extends Component {
  state = {
    isOpen: '' //那一项展开
  }
  // 传给子组件
  changeOpen = (name) => {
    this.setState({ isOpen: name })
  }
  render() {
    let { isOpen } = this.state;
    return (
      <div>
        <div className="friend-list">
          {
            Object.keys(data).map((item, index) => {
              return (
                <Dl
                  key={index}
                  name={item}
                  value={data[item]}
                  isOpen={isOpen}
                  changeOpen={this.changeOpen}
                />
              )
            })
          }
        </div>
      </div>
    );
  }
};
.friend-list {
  border: 1px solid #000000;
  width: 200px;
}

.friend-group dt {
  padding: 10px;
  background-color: rgb(64, 158, 255);
  font-weight: bold;
}

.friend-group dd {
  padding: 10px;
  display: none;
}

.friend-group.expanded dd {
  display: block;
}

.friend-group dd.checked {
  background: green;
}
  1. 在App.js编写要发送的数据
import React, { Component } from "react";
import FriendList from "./FriendList";
import { Provider } from './context'
class App extends Component {
  render() {
    return (
      <Provider value={{ info: '猥琐发育' }}>
        <div>
          <FriendList
          />
        </div>
      </Provider>
    )
  }
}
export default App;

React组件核心_第1张图片

Context.Provider

每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化。

Provider 接收一个 value 属性,传递给另一个组件,一个 Provider 可以和多个组件有对应关系。

多个 Provider 也可以嵌套使用,里层的会覆盖外层的数据

Context.Consumer

这个方法中的React 组件也可以订阅到 context 变更。这能让你在函数式组件中完成订阅 context。

这个函数接收当前的 context 值,返回一个 React 节点。传递给函数的 value 值等同于往上组件树离这个 context 最近的 Provider 提供的 value 值。

如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。

六 非受控组件与受控组件

非受控组件

类似于单向数据流,只可以数据改变视图 defaultValue

import React, { Component } from "react";

class App extends Component {
  state = {
    info: '猥琐发育'
  }
  render() {
    let { info } = this.state;
    return (
      <div>
        <input type="text" defaultValue={info} />
        <button onClick={() => { console.log(info); }}>点我</button>
      </div>
    )
  }
}
export default App;

React组件核心_第2张图片

受控组件

类似vue双向数据绑定,数据和视图之间可以相互影响

import React, { Component } from "react";

class App extends Component {
  state = {
    info: '猥琐发育'
  }
  render() {
    let { info } = this.state;
    return (
      <div>
        <input type="text" value={info} onChange={({ target }) => {
          this.setState({
            info: target.value
          })
        }} />
        <button onClick={() => { console.log(info); }}>点我</button>
      </div>
    )
  }
}
export default App;

React组件核心_第3张图片


总结

React的组件化极大的便利了我们的开发效率

你可能感兴趣的:(react)