React 内部数据 state v.s. 外部数据 props

目录

1、添加外部数据 props

2、使用内部数据 state

3、复杂 state 的具体使用

4、关于两种组件的注意事项


1、添加外部数据 props

和 Vue 类似,React 的组件之间也需要进行传值。父组件需要在 子组件的占位符 上通过 赋值 的形式将值传递给子组件,子组件通过 props 属性拿到父组件传递的值,但是函数组件和类组件在使用外部数据时会有所不同:

  • 类组件  可在其 constructor 接收父组件传递来的数据,直接通过 this.props.xxx 读取 父组件传递给子组件的 xxx 信息。
  • 函数组件 可在其 参数 中接收到父组件传递来的数据,然后直接 读取 参数 props.xxx 即可。

在组件传值时,无论是使用函数组件还是类组件,如果是字符串 " ",如果是变量用:{ }。下面我们看一个例子:

import React from "react";
import ReactDOM from "react-dom";

function App() {
  let n = 1;
  return (
    <div className="App">
      爸爸
      <Son messageForSon="儿子你好" />    <!--如果给子组件需要传递变量:messageForSon={n}-->
    </div>                               <!--如果给子组件需要传递函数:onClick={this.onClick}-->
  );                                     <!--外部数据被包装为一个对象{messageForSon:'',...}-->
}

class Son extends React.Component {      <!--类组件-->
  constructor(props){    <!--如果没有额外的代码,这三行可省略,不写也可以拿到外部数据-->
    super(props);        <!--如果没有额外的代码,这三行可省略,不写也可以拿到外部数据-->
  }                      <!--这样做的效果就是,this.props就是外部数据的地址了-->
  render() {
    return (
      <div className="Son">
        我是儿子,我爸对我说「{this.props.messageForSon}」<!--this.props.xxx 获取外部数据-->
        <Grandson messageForGrandson="孙贼你好" />       <!--如果给子组件需要传递变量:messageForSon={n}-->
      </div>
    );
  }
}

const Grandson = props => {    <!--函数组件可以接收外部传来的 props,这里的props是形参-->
  return (
    <div className="Grandson">
      我是孙子,我爸对我说「{props.messageForGrandson}」<!--props.xxx 获取外部数据-->
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

概括 props 的作用:props 是用来接收父组件传递来的数据,但是这个数据是只读的,不能改写。当然 props 不仅可以接收外部数据,它还可以接收 外部函数,该函数一般是父组件传递过来的,子组件可以在恰当的时机对其进行调用。

2、使用内部数据 state

Vue 的组件内部数据声明在其构造选项 options 的 data 属性中,通过 {{ }} this.xxx 对内部数据进行读写。React 的内部数据则是需要用 state 声明,和使用外部数据 props 一样,React 中类组件和函数组件在使用内部数据时是有所不同的:

  • 类组件 在 constructor() 构造器中通过 this.state = { 键值对 } 的形式声明内部数据。在组件内通过 this.state.xxx  的方式读取组件的内部数据,使用 this.setState({ 键值对 }) 改写组件内部数据。注意:this.setState() 是异步的,不会立即改变 n。
  • 函数组件 用 React.useState(初始值) 返回数组的方式来声明内部数据:const [n, setN] = React.useState(0);。其中数组的第一项为其内部数据 n,第二项 setN() 是改写数据 n 的方法。注意:setN() 是异步的,而且它会声明一个新的 n 覆盖旧的 n。
import React from "react";
import ReactDOM from "react-dom";

function App() {
  return (
    <div className="App">
      爸爸
      <Son />
    </div>
  );
}

class Son extends React.Component {//类组件
  constructor() {                  //需要在constructor()构造器中
    super();
    this.state = {                 //通过 this.state = {} 的形式声明内部数据
      n: 0
    };
  }
  add() {                                   //this.state.n += 1 不行,不像Vue
    this.setState({ n: this.state.n + 1 }); //因为React没有对内部数据做监听,只能调用 this.setState()
                                            //最好使用这种:this.setState( (state) =>{ return state.n + 1});
  }                                         //因为这种方式写两遍可以实现 +2,但是对象的方式却不行
  render() {
    return (
      <div className="Son">
        儿子 n: {this.state.n}    <!--类组件通过 this.state.n 的形式读数据-->
        <button onClick={() => this.add()}>+1</button>
        <Grandson />
      </div>
    );
  }
}

const Grandson = () => {
  const [n, setN] = React.useState(0);
  return (
    <div className="Grandson">
      孙子 n:{n}
      <button onClick={() => setN(n + 1)}>+1</button>
    </div>
  );
};

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

补充:为什么在 React 的类组件中,不能通过 this.state.xxx +=1;这种形式改写其内部属性?因为 React 不像 Vue 对其内部数据进行了篡改和监听,一旦监听到数据改变就会去刷新视图。React 如果通过 this.state.xxx +=1;这种形式改写数据,视图是不知道的,自然就不会刷新视图了,所以 React 需要通过 this.setState({ 键值对 }) 的形式改写组件内部数据并刷新视图。

建议:在 class 类组件中,如果需要 this.setState() 改写数据,其内部最好传递一个 函数,让函数去改写数据。比如:this.setState({ n: this.state.n + 1 }); 最好能改写成 this.setState( (state) => { return { n: state.n + 1 } }); 的形式。这是因为 this.setState() 是一个异步更新 UI 的过程,如果让你在 this.setState() 下打印 n 的值,代码会先执行打印操作,再去更新 UI,这就导致你拿到的 n 是一个旧的值。如果改成函数的形式,你就可以再函数内部进行打印 n 的操作,如:this.setState( (state) => { const n = state.n+1; return { n: n }); 。

React 的理念是 数据不可变,它会通过 setN() 声明 新数据,而 Vue 的理念是 数据响应式,它提倡改变原数据。

3、复杂 state 的具体使用

上面简单介绍了在类组件和函数组件中 state 的声明及读写方法,但是如果 state 中的数据比较复杂,则情况会有些不一样,比如 state 中有多种类型的数据,包括对象类型,对象中还包括一些简单数据。下面我们来具体分析一下。

类组件 中如果 state 含有多个数据,在改变其中一个数据值的情况下,其它数据会默认自动 延用 之前的值。需要注意的是,上述这种延用机制并不是绝对的,类组件的 setState() 只能自动合并第一层属性(shallow merge): React 只会检查新 state 和旧 state 第一层的区别,并把新 state 缺少的数据从旧 state 里拷贝过来。比如 state 中有一个 person 对象,对象中有 name 和 age 两个属性。这种情况下如果只通过 setState() 改写 name 属性,则 age 属性会被设置为 undefined。

解决第二层合并的问题,可以在 person 对象内部属性前写:...this.state.person 。或者在修改属性的函数内部写上:const user=Object. assign({ }, this. state. user); 或 const user={ ...this.state.user }; 代码。

class Son extends React.Component {
  constructor() {
    super();
    this.state = {   //state中含有 m 和 n 两个数据
      n: 0,
      m: 0
    };
  }
  addN() {
    this.setState({ n: this.state.n + 1 });    //m 不会被覆盖为 undefined,会自动延用之前的值
  }              //或者写成:this.setState({...this.state , n: this.state.n + 1 })
  addM() {
    this.setState({ m: this.state.m + 1 });    //n 不会被覆盖为 undefined,会自动延用之前的值
  }              //或者写成:this.setState({...this.state , m: this.state.m + 1 })
  render() {
    return (
      <div className="Son">
        n: {this.state.n}
        <button onClick={() => this.addN()}>n+1</button>  <!--点击按钮调用 addN() 使 n+1-->
        m: {this.state.m}
        <button onClick={() => this.addM()}>m+1</button>  <!--点击按钮调用 addM() 使 m+1-->
      </div>
    );
  }
}

函数组件 中如果 state 中有多个数据。需要分情况讨论。如果是下面第一种声明变量的形式(m 和 n 分开),那就单个设置就好了,不会有别的问题。如果将数据声明在一个对象中的话({ n:0,m:0 }),则在改变其中一个数据值的情况下,其它数据 不会 默认自动 延用 之前的值,如果非要使用这种方式,可以在 setState() 时将 state先拷贝一份:setState({...state,n:state.n+1})。

函数组件在二级属性上也是不会进行合并的,如果修改一个对象类型的数据的第一个属性值,其余的会被置为 undefined,需要开发者手动使用 ... 操作符进行手动合并。

const Grandson = () => {
  const [n, setN] = React.useState(0);    //不推荐将 m、n 写到一个对象中
  const [m, setM] = React.useState(0);    //那样在设置单个属性时,另一个会被置为 undefined
  return (                                //const [state, setState] = React.useState({n:0,m:0}); 不推荐
    <div className="Grandson">            //如果这种形式,在每次setState时需要:setState({...state,n:state.n+1})
      n:{n}
      <button onClick={() => setN(n + 1)}>n+1</button>
      m:{m}
      <button onClick={() => setM(m + 1)}>m+1</button>
    </div>
  );
};

4、关于两种组件的注意事项

  • 类组件中 this.state.n += 1无效?

解释:其实经过上述这行代码之后的 n 已经改变了,只不过 UI 不会自动更新而已,需要调用 setState() 才会触发 UI 更新(异步更新)。因为 React  没有像 Vue 监听 data 一样监听 state,React 不知道数据什么时候发生改变,所以只能开发人员手动调用 seteState() 刷新视图。

  • 类组件中的 setState() 是异步更新 UI 的

在调用 setState() 之后,state 不会马上改变,立马读取 state 其实是旧的值。更推荐的方式是 setState(函数) 的形式。

  • 类组件 this.setState(this.state)不推荐?

React 的理念是数据不可变,它不推荐开发人员修改旧的 state(不可变数据),然后将修改完的 this.state 传递给 setState() 。而是推荐这种 setState({ n:this.state.n+1 })方式,声明新的 n 数据。 

  • 函数组件 setx(新值)

函数组件和类组件在改写内部数据方面比较相似,也要推荐开发人员通过 setx(新值)来更新 UI。

  • 函数组件跟类组件不同的地方

类组件中无论是使用数据还是调用方法,都需要使用 this 关键字,而函数组件没有 this,一律用参数和变量。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值