2022-03-28 React 第二天

一、组件状态:State和SetState

状态(state) 即数据,函数组件又叫无状态组件,只负责数据展示;类组件又叫有状态组件,负责更新UI。

1.state基本使用

(1)在类组件中直接定义state为一个对象。(或者在类组件中声明构造函数)

state = {count:0}

(2)修改数据必须用组件实例上的 setState()方法

this.setState({ count: this.state.count + 1 })

(3)修改哪些数据,就在setState配置对象里写哪些数据。

// 类组件中的状态:state数据
class StateBox extends React.PureComponent {
  // 方法一:为类组件添加状态
  // constructor (props) {
  //   super(props);
  //   this.state = {
  //     count: 0
  //   }
  // }

  // 方法二:state简写形式(推荐):这种写法是类支持的
  state = {
    count: 1
  }

  render () { return  <div> </div> }
}
ReactDOM.render(<StateBox />, document.getElementById('root'))
2. 受控组件

受控组件:所有输入型的DOM,其值会随着输入被维护到state中,就是受控组件。例如:

  • HTML中的表单元素是可输入的,也就是有自己的可变状态
  • 而React中可变状态通常保存在state中,并且只能通过setState() 方法来修改
  • React将state与表单元素值value绑定在一起,由state的值来控制表单元素的值

(1)在state中添加一个状态,作为表单元素的value值
(2)给表单元素绑定change事件,将表单元素的值设置为state的值

// 受控组件
class MyForm extends React.Component {
  state = {
    input: '我是普通input表单',
    textarea: '我是文本框',
    select: '纽约',
    checkbox: false
  }
  handleChange = (e) => {
    const value = e.target.type === 'checkbox' ? e.target.checked : e.target.value
    this.setState({ [e.target.name]: value })
  }
  render () {
    return (
      <div>
        <input name="input" value={this.state.input} onChange={this.handleChange} />
        <textarea name="textarea" value={this.state.textarea} onChange={this.handleChange}></textarea>
        <select name="select" value={this.state.select} onChange={this.handleChange}>
          <option>商丘</option>
          <option>纽约</option>
          <option>洛杉矶</option>
        </select>
        <input
          name="checkbox"
          type="checkbox"
          checked={this.state.checkbox}
          onChange={this.handleChange}
        />
      </div>
    )
  }
}
3.非受控组件

(1)调用 React.createRef() 方法创建ref对象
(2)将创建好的 ref 对象添加到文本框中
(3)通过ref对象获取到文本框的值

class App extends React.Component {
    constructor(){
        super()
        //创建 ref
        this.txtRef = React.createRef()
    }
    // 获取文本框的值
    getTxt =() => {
        console.log(this.txtRef.current.value)
    }
    render(){
        return (
          <div>
            <input type ="text" ref={this.txtRef} />
            <button onClick ={this.getTxt}>获取值</button>
          </div>
        )
    }
}
4. 页面中用到的静态数据

可以写在 类组件之前
使用这个数据时 不加this,直接写变量名。

捕获.PNG

二、setState详细说明

修改state数据;更新UI视图。

1.setState()异步更新

(1)setState()更新数据是 异步 的。
(2)可以 多次 调用setState(),但是后面的setState 不能依赖前面setState的值。
(3)多次调用setState,只会触发 一次render()。

2.setState()推荐语法

this.setState((state,props)=>{
return { }
})

(1)通过参数state和props可以获取到 最新的 state数据和props数据。
(2)解决了 多次调用setState() 时,后面的setState不能依赖前面的setState数据的问题。

3.setState()第二个参数

this.setState((state,props)=>{ },()=>{ })

(1)第二个参数是一个 回调函数。
(2)这个回调函数会在 数据更新后,也就是页面重新渲染后 立即执行。

// setState()推荐语法
this.setState(
  (state, props) => {
    return {
      count: state.count + 1
    }
  },
  () => {
    console.log('数据更新后,也就是页面重新渲染后,会立即执行这个回调函数');
  }
)

三、props基本使用

  • 组件是封闭的,要 接收外部数据 应该通过props来实现
  • props的作用:接收传递给组件的数据
  • 传递数据:给组件标签添加属性
1.函数组件

函数组件通过 参数props 接收外部传递的数据。

const Dog = (props) => {
  // 注意:props是一个对象,不要直接放到jsx中渲染
  console.log(props);
  return (
    <div>{props.name}</div>
  )
}
ReactDOM.render(<Dog name="狗" />, document.getElementById('root'))
2.类组件

(1)类组件通过 this.props 获取外部传递的数据。

class Cat extends React.Component {
  // 如果类组件中有构造函数,props需要作为参数传递给super()函数
  constructor (props) {
    super(props)
    console.log(props);
  }
  render () {
    return (
      <ul>
        <li>{this.props.age}</li>
        <li>{this.props.friends[1]}</li>
        <li>{this.props.fn()}</li>
        <li>{this.props.tag}</li>
      </ul>
    )
  }
}
// 可以传递各种类型数据:数组、函数、jsx
ReactDOM.render(
  <Cat
    name="狗"
    age={6}
    friends={['Tom', 'Jerry']}
    fn={() => console.log('这是一个函数')}
    tag={<p>这是一个jsx</p>}
  />,
  document.getElementById('root')
)

(2)传递数据时的写法
① React中通过 展开运算符展开对象,一次传递多个数据。
② 下面的 { } 代表的是里面要写js语法;... 代表的是展开运算符,要展开后面的对象。
原生js 中,展开运算符 不能展开对象;只有 React中 ,可以用于 标签上数据的传递。
{...obj} 的形式代表 复制 一个新的对象,相当于深拷贝。

class Index extends React.Component {
  state = {
    name: '',
    age:''
  }
  render () {
    // 通过展开运算符,直接将一个对象传递给子组件
    return <Son {...this.state}></Son>
  }
}
3.传递的数据类型

(1)可以是字符串、数字、数组、函数、jsx等。示例如上。
(2)props是 只读属性,不能对值进行修改.
(3)使用类组件时,如果写了构造函数,应该将props传递给super(),否则,无法在构造函数中获取到this.props,其他的地方是可以拿到的。

四、组件通信

1.父向子传值

(1)父组件调用子组件时,给子组件绑定 自定义属性。
(2)自定义属性值为父组件的 私有数据。
(3)子组件内通过 props 进行接收。(两种组件接收props的方法见上面)

// 父向子传值
class Father extends React.Component {
  state = {
    msg: '我是父组件的数据'
  }
  render () {
    return (
      //父组件绑定自定义属性
      <Son msg={this.state.msg} />
    )
  }
}
class Son extends React.Component {
  render () {
    return (
      //子组件通过props接收
      <div>{this.props.msg}</div>
    )
  }
}
2.子向父传值

(1)父组件调用子组件时,绑定一个 自定义事件。
(2)子组件内触发自定义事件,通过参数传递数据。
(3)自定义事件要提前在父组件内声明,通过 参数 可以得到子组件传递的数据。

// 子向父传值
class Father extends React.Component {
  // 父组件内声明自定事件
  showMsg = data => {
    console.log('父组件拿到了子组件数据:', data);
  }
  render () {
    return (
      // 父组件调用子组件时绑定自定义事件
      <Son getMsg={this.showMsg} />
    )
  }
}
class Son extends React.Component {
  state = {
    msg: '我是子组件的数据'
  }
  handleClick = () => {
    // 子组件触发自定义事件并传递数据
    this.props.getMsg(this.state.msg)
  }
  render () {
    return (
      <button onClick={this.handleClick} >点我</button>
    )
  }
}
3.兄弟组件传值

(1)在两兄弟的最近的公共父组件内存放 共享状态
(2)公共父组件职责:提供共享状态 ;提供操作共享状态的方法
(3)要通讯的子组件只需要通过props接收状态或操作状态的方法

// 兄弟组件传值
class Father extends React.Component {
  // 父组件提供共享数据
  state = {
    number: 0
  }
  // 父组件声明自定义事件
  handleClick = (data) => {
    this.setState({ number: this.state.number + data })
  }
  render () {
    return (
      <div>
        {/* 父组件向子组件传递数据 */}
        <Child1 number={this.state.number} />
        {/* 父组件通过自定义事件获取子组件传递的值 */}
        <Child2 onAdd={this.handleClick} />
      </div>
    )
  }
}
class Child1 extends React.Component {
  render () {
    return (
      // 子组件接收父组件的数据
      <h1>{this.props.number}</h1>
    )
  }
}
class Child2 extends React.Component {
  handleClick = () => {
    this.props.onAdd(5)
  }
  render () {
    return (
      // 子组件触发自定义事件,修改父组件的值
      <button onClick={this.handleClick}>点我</button>
    )
  }
}
ReactDOM.render(<Father />, document.getElementById('root'))
4.Context基本使用

如果嵌套层级比较深,父组件可以用 Context 将数据传递给后代子组件。
(1)调用 React.createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件

const { Provider, Consumer } = React.createContext()

(2)顶级父组件 中,用<Provider></Provider>包裹子组件,并通过 value 属性传递数据。
(3)子组件中,在<Consumer></Consumer>中,有一个回调函数,其参数为接收的数据。

// Context基本使用
// 调用 React.createContext() 创建 Provider(提供数据) 和 Consumer(消费数据) 两个组件
const { Provider, Consumer } = React.createContext()
class ZuXian extends React.Component {
  render () {
    return (
      // 使用Provider组件,并通过value属性向子代传递数据
      <Provider value="我是你祖宗">
        <SunZi></SunZi>
      </Provider>
    )
  }
}
class SunZi extends React.Component {
  render () {
    return (
      // 子组件以这种形式获取数据
      <div>
        <Consumer>
          {
            data => <span>{data}</span>
          }
        </Consumer>
      </div>
    )
  }
}
ReactDOM.render(<ZuXian></ZuXian>, document.getElementById('root'))
5.props的children属性
  • children属性: 表示组件标签的子节点,当组件标签有子节点时,props就会有该属性
  • children属性与普通的props一样,值可以是任意值(文本、react元素、组件、甚至是函数)
// props的children属性
const Children = (props) => {
  return (
    <div>{props.children}</div>
  )
}
ReactDOM.render(<Children>我是组件的子节点</Children>, document.getElementById('root'))
6.props校验
  • 对于组件来说,props是外来的,无法保证组件使用者传入什么格式的数据
  • 如果传入的数据不对,可能会导致报错
  • props校验:允许在创建组件的时候,指定props的类型、格式等
  • 作用:捕获使用组件时因为props导致的错误,给出明确的错误提示,增加组件的健壮性

(1)安装包 prop-types

yarn add prop-types | npm i prop-types

(2)页面导入prop-types 包

import 自定义名称 from 'prop-types'

(3)使用 组件名.propTypes={} 来给组件的props添加校验规则。(propTypes是固定的,由React提供)
(4)校验规则通过 PropTypes对象 来指定。(PropTypes也是固定的,由依赖包提供)

// props校验
class JiaoYan extends React.Component {
  render () {
    return (
      <div>{this.props.color}</div>
    )
  }
}
JiaoYan.propTypes = {
  color: PropTypes.String,
  age: PropTypes.number.isRequired, //数字类型且必填
  job: PropTypes.shape({
    time: PropTypes.String,
    money: PropTypes.number
  })
}
ReactDOM.render(
  <JiaoYan
    color='pink'
    age={18}
  ></JiaoYan>,
  document.getElementById('root')
)
常见的约束规则
  • 创建的类型: array、bool、func、number、object、string

  • React元素类型:element

  • 必填项:isRequired

  • 特定结构的对象: shape({})

  • 更多的约束规则

    props-约束规则.png

7.props默认值

通过 组件名.defaultProps对象 指定props的默认值。如果传递了该值,则默认值被覆盖。

JiaoYan.defaultProps = {
  pagesize: 10
}
ReactDOM.render(
  <JiaoYan
    color='pink'
    age={18}
  ></JiaoYan>,
  document.getElementById('root')
)
8. peops校验与默认值写到类中
class Index extends React.Component {
  render () {
    return  <div></div>
  }
// 用 static修饰后,propsTypes对象和defaultProps对象被挂载到类上
static propTypes = {}
static defaultProps = {}

五、类组件中的 ref

1.字符串形式的ref(老版本)

(1)用法类似于vue2.0,在组件上添加 ref 属性。
(2)在方法中用 this.refs.名称(没有$) 获取组件实例。

class Index extends React.Component {
  showInput = () => {
    console.log(this.refs.inputRef);
  }
  render () {
    return (
      <div>
        <input type="text" onBlur={this.showInput} ref="inputRef"/>
      </div>
    )
  }
}
2.回调函数形式的ref (中间版)

(1)回调函数:自己定义的函数,别人来调用。
(2)在组件上添加 ref 属性,属性值是一个 回调函数。
(3)通过回调函数的 参数 拿到组件实例,直接 挂载到this上。

class Index extends React.Component {
  showInput = () => {
    const { btnRef } = this
    console.log(btnRef);
  }
  render () {
    return (
      <div>
        <button onClick={this.showInput} ref={(currentNode)=>this.btnRef=currentNode}>点击</button>
      </div>
    )
  }
}

(4)关于回调 refs 的说明
如果 ref 回调函数是以 内联函数(就是上面的方式) 的方式定义的,在 更新过程中 它会被执行 两次,第一次传入参数 null,紧接着第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 先清空旧的 ref 再设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是 无关紧要的。

class Index extends React.Component {
  showInput = () => {
    const {btnRef2} = this
    console.log(btnRef2);
  }
  // 挂载ref到this
  setRef = (currentNode) => {
    this.btnRef2 = currentNode
  }
  render () {
    return (
      <div>
        <button onClick={this.showInput} ref={this.setRef}>点我</button>
      </div>
    )
  }
}
3.createRef()

(1)用 createRef()函数创建一个 ref容器,挂载到 类的实例对象上。
(2)在组件上添加ref属性,属性值就是 ref容器,React会把组件实例放到ref容器中。

class Index extends React.Component {
  // 创建ref容器
  btnRef3 = React.createRef()

  showInput = () => {
    console.log(this.btnRef3.current);
  }

  render () {
    return (
      <div>
        <button onClick={this.showInput} ref={this.btnRef3}>点击一下</button>
      </div>
    )
  }
}

六、事件处理函数

1.类组件中,事件处理函数的写法
  • render()函数中的this指向 组件实例;(constructor构造函数也是)
  • 事件处理函数中的this是 undefined,无法调用组件实例的setState()方法

方案一:利用箭头函数(了解)

在箭头函数中返回事件处理函数的调用,将render()函数的this传递给事件处理函数

// 方案一:利用箭头函数
class ChangeThis1 extends React.PureComponent {
  state = {
    count: 1
  }
  handleClick () {
    this.setState({ count: this.state.count + 2 })
  }
  render () {
    return (
      <div>
        <div>{this.state.count}</div>
        <button
          //在箭头函数中返回事件处理函数的调用,将render()函数的this传递给事件处理函数
          onClick={() => this.handleClick()}
        >
          +1
        </button>
      </div>
    )
  }
}
ReactDOM.render(<ChangeThis1 />, document.getElementById('root'))

方案二:通过bind方法,更改事件处理函数中的this(了解)

在构造函数中将事件处理函数中的this绑定为组件实例

// 方案二:通过bind方法,更改事件处理函数中的this(了解)
class ChangeThis2 extends React.PureComponent {
  constructor () {
    super()
    this.state = {
      count: 1
    }
    // 在构造函数中将事件处理函数中的this绑定为组件实例
    // 1.下面代码是右侧内容赋值给左侧,此时this代表的 【ChangeThis2】 实例对象上并没有handleClick函数,
    // 2.而是通过原型链拿到了原型对象上的 【handleClick】函数,并同过bind方法改变了this指向。
    // 3.bind方法返回一个新的函数,此时在this实例对象上我们手动创建了【newHandler】函数进行接收。
    // 4.最终事件绑定的处理函数,是实例对象上的【newHandler】。
    this.newHandleClick = this.handleClick.bind(this)
  }
  handleClick () {
    this.setState({ count: this.state.count + 3 })
  }
  render () {
    return (
      <div>
        <div>{this.state.count}</div>
        <button
          onClick={this.newHandleClick}
        >
          +1
        </button>
      </div>
    )
  }
}
ReactDOM.render(<ChangeThis2 />, document.getElementById('root'))

方案三:利用箭头函数形式的class实例方法

类组件绑定事件、修改state数据,参照这个即可

// 方案三:利用箭头函数形式的class实例方法
class ChangeThis3 extends React.PureComponent {

  state = {
    count: 1
  }
  // 直接将事件处理函数改为箭头函数即可
  handleClick = () => {
    this.setState({ count: this.state.count + 10 })
  }
  render () {
    return (
      <div>
        <div>{this.state.count}</div>
        <button
          onClick={this.handleClick}
        >
          +1
        </button>
      </div>
    )
  }
}
ReactDOM.render(<ChangeThis3 />, document.getElementById('root'))

注意:
(1)这是给事件添加处理方法需要用箭头函数,如果是获取接口数据的方法,可以直接写在配置对象中。
(2)onClick={this.handleClick} 是将 handleClick函数本身 作为点击事件的处理函数。
(3)onClick={this.handleClick()} 是将handleClick函数的 返回值 作为点击事件的处理函数。
(4)onClick={(record)=>{this.handleClick(record)}} 是将一个箭头函数作为点击事件的处理函数,箭头函数的参数可以拿到事件对象或其他内部实参

class Index extends React.Component {
  handleClick1 = (event) => {
    console.log('输入框的value是:',event.target.value);  // false
  }
  handleClick2 = (a) => {
    return (event) => {
      console.log(a); // '自定义内容'
      console.log('输入框的value是:',event.target.value); // false
    }
  }
  handleClick3 = (event,a) => {
    console.log('输入框的value是:', event.target.value);  // false
    console.log(a); // false
  }
  render () {
    return (
      <div>
        <button value="false" onClick={this.handleClick1}>按钮1</button>
        <button value="false" onClick={this.handleClick2('自定义参数')}>按钮2</button>
        <button value="false" onClick={ (event)=>this.handleClick3(event,'自定义参数')}>按钮3</button>
      </div>
    )
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容