React从入门到精通教程02

React从入门到精通教程

往期回顾:

  1. React从入门到精通教程01
  2. React从入门到精通教程02
  3. React从入门到精通教程03
  4. React从入门到精通教程04

组件和Props

  1. 概述

    组件是React中一个非常重要的概念,React将UI页面拆分为一个个的独立单元,每一个独立单元都是独立的,这个独立单元叫做组件。使用组件我们可以很方便的复用一些重复代码,为我们的工作带来了极大的便利。

  2. 函数组件与class组件

    定义组件有两种方式,使用函数或者ES6 里面的class

    // 函数组件,接受一个参数`prop`并返回一个React元素
    function Welcome(props) {
      return <h1>Hello, {props.name}</h1>;
    }
    // class组件
    class Welcome extends React.Component {
      render() {
        return <h1>Hello, {this.props.name}</h1>;
      }
    }
    

    后面会详细的讲解这两种定义方式的好处和坏处以及应用场景

  3. 渲染我们的自定义组件

    我们可以渲染原先就存在的HTML标签元素,也可以渲染我们自定义的组件。当 React 元素为用户自定义组件时,它会将 JSX 所接收的属性(attributes)以及子组件(children)转换为单个对象传递给组件,这个对象被称之为 “props”。

    例如,这段代码会在页面上渲染 “Hello, Sara”:

    function Welcome(props) {  
        return <h1>Hello, {props.name}</h1>;
    }
    
    const element = <Welcome name="Sara" />;
    ReactDOM.render(
      element,
      document.getElementById('root')
    );
    // 自定义组件一般以大写字母开头,上面我们定义了一个Welcome组件,这个组件传入了一个对象props,对象里面有一个属性name
    

    我们来回顾一下这个例子中发生了什么:

    1. 我们调用 ReactDOM.render() 函数,并传入 <Welcome name="Sara" /> 作为参数。
    2. React 调用 Welcome 组件,并将 {name: 'Sara'} 作为 props 传入。
    3. Welcome 组件将 <h1>Hello, Sara</h1> 元素作为返回值。
    4. React DOM 将 DOM 高效地更新为 <h1>Hello, Sara</h1>
  4. 组件中嵌套组件
    1. 组件可以在其输出中引用其他组件。这就可以让我们用同一组件来抽象出任意层次的细节。按钮,表单,对话框,甚至整个屏幕的内容:在 React 应用程序中,这些通常都会以组件的形式表示。组件中嵌套组件的方式为我们写代码提供了更高的复用性和灵活性
    function UserIfo(porps)
       {
         return (
            <div>
              <img src={porps.user.avatar} />
              <p>name: {porps.user.name}</p>
            </div>
          )
        }
    function Description(props){
          return <h6>{props.description}</h6>
        }
    const peopleIfo = {
          user: {
            avatar: 'https://placekitten.com/g/64/64',
            name: 'kobe',
          },
          description: 'my name is kobe',
        }
    function Comment(props)
        {
          console.log(props)
          return (
            <div>
              <h3>{`我是一个复合组件,传进来的值为${JSON.stringify(props)}`}</h3>
              <UserIfo user={props.peopleIfo.user} />
              <Description description={props.peopleIfo.description} />
            </div>
          )
        }
    ReactDOM.render(<Comment peopleIfo={peopleIfo}/>, document.getElementById('root'))
    

    结果如下:

    image-20210615203452731

  5. 提取组件

    我们一个组件最好是不要设计的特别大,不然复用性比较低,如果我们设计的组件比较大,我们就要适当的进行组件拆分,将一个大组件拆分为很多个小组件

  6. props的只读性
    1. 纯函数:不会更改入参的函数,多次调用下相同的入参始终返回相同的结果

    2. 非纯函数:会更改自己的入参

      function sum(a, b) {
        return a + b;
      } // 纯函数
      function withdraw(account, amount) {
        account.total -= amount;
      } // 非纯函数
      

      React组件也可以看作是一个函数,它接受一个props作为参数,并返回一个要展示在界面上的对象。React 非常灵活,但它也有一个严格的规则:

      所有 React 组件都必须像纯函数一样保护它们的 props 不被更改。

      但是UI是动态的,我们为了保证这种动态性,React就引入了state来做组件的状态管理

state

  1. 概述:

    我们之前所接触到的组件想要被更新只有通过Reader()函数的调用才能被更新,那么有没有可能我们让组件自己更新,而不用每次我们都显式的调用Render()函数呢,答案是肯定的,其实传入组件的props只要发生改变那么组件就会发生更新,但是props对于子组件来说只是可读的,子组件不能管理,只有父组件改变传入的props改变了才会更新,这样对于我们来说是非常不友好的,我们希望我们定义的组件(例如之前的时钟组件)有自己可以管理的状态,并且这个状态发生改变组件也能更新,这个状态就是我们接下来要说的state

  2. 特点:

    state是组件私有的,只有组件可以访问得到,对于外部不可见,组件与组件之间的state也是互不影响的。同时state完全受控于当前的组件。但是要注意,只有用class定义的组件才有state,所以类组件也被叫做有状态组件,函数组件是没有state的,因此也被叫做无状态组件

  3. 使用state复写之前的时间组件
    // html 容器
    <div id="root"></div>
    // js
    class Clock extends React.Component {
          constructor(props) {
            super(props)
            this.state = {
              date : new Date(),
            }
          };
          componentDidMount(){ // 当 Clock 组件第一次被渲染到 DOM 中的时候,就为其设置一个计时器。这在 React 中被称为“挂载(mount)”。
            this.timer = setInterval(() => {
              this.setState({  // 更新state
                date: new Date(),  
              })
            })
          };
          componentWillUnmount() { // 当 DOM 中 Clock 组件被删除的时候,应该清除计时器。这在 React 中被称为“卸载(unmount)”。
            clearInterval(this.timer)
          }
          render() {
              return (
                <div>
                  <h1>Hello World!</h1>
                  <h2>Now it is { this.state.date.toLocaleTimeString() }</h2>
                </div>
              );
          }
        }
        ReactDOM.render(<Clock />, document.getElementById('root'))
    

    梳理一下调用流程:

    1. <Clock /> 被传给 ReactDOM.render()的时候,React 会调用 Clock 组件的构造函数。因为 Clock 需要显示当前的时间,所以它会用一个包含当前时间的对象来初始化 this.state。我们会在之后更新 state。
    2. 之后 React 会调用组件的 render() 方法。这就是 React 确定该在页面上展示什么的方式。然后 React 更新 DOM 来匹配 Clock 渲染的输出。
    3. Clock 的输出被插入到 DOM 中后,React 就会调用 ComponentDidMount() 生命周期方法。在这个方法中,Clock 组件向浏览器请求设置一个计时器来每秒调用一次组件的 tick() 方法。
    4. 浏览器每秒都会调用一次 tick() 方法。 在这方法之中,Clock 组件会通过调用 setState() 来计划进行一次 UI 更新。得益于 setState() 的调用,React 能够知道 state 已经改变了,然后会重新调用 render() 方法来确定页面上该显示什么。这一次,render() 方法中的 this.state.date 就不一样了,如此以来就会渲染输出更新过的时间。React 也会相应的更新 DOM。
    5. 一旦 Clock 组件从 DOM 中被移除,React 就会调用 componentWillUnmount() 生命周期方法,这样计时器就停止了。

    这里面有几个关键点:

    1. 必须在constructor里面调用super(props),虽然使用super()也可以,但是可能会出现问题,解释为什么需要:调用super(),这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。所以必须先调用super才可以使用this。如果子类没有定义constructor方法,这个方法会被默认添加。那么为什么要调用super(props),因为我们调用组件的时候会实例化一个对象,调用这个父类的component会默认帮我们把props添加到this实例上,方便我们在自定义组件使用。 友链
    2. 组件的生命周期:生命周期是React中的一个重要概念,react官方提供了很多生命周期的钩子函数供我们使用,为我们开发带来了很大的遍历,关于详细的生命周期我会在另一篇博客单独讲

    使用State要注意的点

    1. 不要直接修改state
      this.state.date = new Date(); // 这种方法唯一能生效的情况就是在构造函数`constructor`中,其余情况均不生效。构造函数是唯一可以给 this.state 赋值的地方:
      this.setState({ date = new Date() }); // 这是在构造函数之外更改state属性的唯一方法
      
    2. State 的更新可能是异步的

      出于性能考虑,React 可能会把多个 setState() 调用合并成一个调用。

      因为 this.propsthis.state 可能会异步更新,所以你不要依赖他们的值来更新下一个状态。

      考虑这个例子:

      class Clock extends React.Component {
            constructor(props) {
              super(props)
              this.state = {
                date : 11,
              }
            };
            componentDidMount(){
              this.setState(
                {date: this.state.date+1}
              );
              this.setState(
                {date: this.state.date+1}
              );
              this.setState(
                {date: this.state.date+1}
              );
              this.setState(
                {date: this.state.date+1}
              );
              this.setState(
                {date: this.state.date+1}
              );
              this.setState(
                {date: this.state.date+1}
              );
            };
            render() {
                return (
                  <div>
                    <h1>Hello World!</h1>
                    <h2>Now it is { this.state.date }</h2>
                  </div>
                );
            }
          }
      

      最后页面上显示的结果为:

      image-20210618173350223

      这个例子可以清楚的看到state的更新是合并了的,要解决这种情况我们可以使用在setState()中使用回调函数的方式来更新我们的state值。

      this.setState(function(state, props) {
        return {
          date: state.date + props.date
        };
      }); // 这个函数用上一个 state 作为第一个参数,将此次更新被应用时的 props 做为第二个参数:
      

      推荐文章

      关于setState是同步还是异步

      setState调用的两种方式1

      setState调用的两种方式2

    3. State 的更新会被合并

      例如,你的 state 包含几个独立的变量:

        constructor(props) {
          super(props);
          this.state = {
            posts: [],      comments: []    };
        }
      

      然后你可以分别调用 setState() 来单独地更新它们:

        componentDidMount() {
          fetchPosts().then(response => {
            this.setState({
              posts: response.posts      });
          });
      
          fetchComments().then(response => {
            this.setState({
              comments: response.comments      });
          });
        }
      

      这里的合并是浅合并,所以 this.setState({comments}) 完整保留了 this.state.posts, 但是完全替换了 this.state.comments

      关于深浅合并

  4. 数据是从上而下流动的(单向的)

    任何的 state 总是所属于特定的组件,而且从该 state 派生的任何数据或 UI 只能影响树中“低于”它们的组件。如果你把一个以组件构成的树想象成一个 props 的数据瀑布的话,那么每一个组件的 state 就像是在任意一点上给瀑布增加额外的水源,但是它只能向下流动。之后我们使用Redux的时候,只用在根组件使用<provider>就可以让整个子组件都是用到我们自己定义的store;

未完待续… …

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值