美文网首页
4-组件与 prop、state

4-组件与 prop、state

作者: 谷子多 | 来源:发表于2018-04-16 20:42 被阅读0次

    语法:

    1. 引入Component
    2. 新建一个类,继承Component
    3. 实现render函数
    4. 在render函数内返回一个jsx,并导出
    5. 入口文件引入该组件
    6. 引入时的名字作为组件标签
    import React,{Component} from 'react';
    export default class hello extends Component{
      render(){
        return (
          <div>
            <h1>组件语法学习</h1>
          </div>
        )
      }
    }
    ----------------------------------------------------------------------
    import Hello from './component/component';
    
    ReactDOM.render(
        <div>
            <Hello></Hello>
            <p
                style={{
                    border : '1px solid #000' 
    
                }}
            >hell,react
            </p>
        </div>,
        document.getElementById('root')
    )
    

    prop

    ReactDOM.render(
        <div>
            <Hello name='小明' age='18' hubby='学习react'>
                <p>我是children</p>
            </Hello>
            <p
                style={{
                    border : '1px solid #000' 
    
                }}
            >hell,react
            </p>
        </div>,
        document.getElementById('root')
    )
    
    import React,{Component} from 'react';
    
    export default class hello extends Component{
      render(){
        let {name,age,hubby} = this.props
        return (
          <div>
            <h1>组件语法学习</h1>
            <p>姓名:{name}</p>
            <p>年龄:{age}</p>
            <p>爱好:{hubby}</p>
            {this.props.children}
          </div>
        )
      }
    }
    

    props.children

    查看如下代码:

    ReactDOM.render(
      <div>
        <Ment num={9} name="Moli">
          <p>网速太卡了!!</p>
        </Ment>
    
      </div>
    
      , document.getElementById('root')
    );
    

    我们在使用 Ment 组件的时候, 我们在标签之间插入了一个 p 标签, 但是你会发现 p 标签的内容并没有渲染到页面上. 💢💢

    事实上, 以下两种写法是等价的: 🌟🌟

    <Ment num={9} name="Moli">
      <p>网速太卡了!!</p>
    </Ment>
    

    // === 这两种写法等价

    <Ment
      num={9}
      name="Moli"
      children={<p>网速太卡了!!</p>}
    />
    

    发现了么, 写在组件标签之间的内容本质上只是给组件传递了一个 children 属性.

    所以, 要想 p 标签显示出来, 就得用上它.

    比如: 在组件实现上, 你可以这样:

    function Ment(props) {
      return (
          <div>
            <h2>Hello {props.name}</h2>
            {props.num} people here!
            {props.children}
          </div>
      )
    }
    

    state: 内部状态

    确定一个共识: 类组件才有内部状态!

    一个组件在某些时候需要作出一些改变. 它不可能总是一成不变的.

    比如一个计数器, 在某一次点击过后, 现实的数字就会改变; 再比如一个时钟程序, 现实的数字随着时间变动.

    现在我们尝试做一个计数器看看.

    这是我们的基础代码:

    import React from 'react';
    import ReactDOM from 'react-dom';
    
    class Counter extends React.Component{
    
      render(){
        return (
          <div>
            <p>目前计数: 0</p>
            <button>计数 +1</button>
          </div>
        )
      }
    }
    
    ReactDOM.render(
      <Counter/>
      , document.getElementById('root')
    );
    

    Counter 组件现在渲染之后是这个样子的:

    image.png

    给组件添加状态

    现在我们改造一下 Counter 组件:

    class Counter extends React.Component{
    
      constructor(props){
        super(props);
        this.state= {
          count: 0
        };
      }
    
      render(){
    
        let { count } = this.state;
    
        return (
          <div>
            <p>目前计数: {count}</p>
            <button>计数 +1</button>
          </div>
        )
      }
    }
    

    定义状态

    我们首先增加了构造函数 constructor():

    constructor(props){
      super(props);
      this.state = {
        count: 0
      };
    }
    

    构造函数接收的第一个参数就是我们之前学的 props;

    我们先使用 super(props); 把组件的 props 传给父类的构造函数, 否则在构造函数里, 即便传递了 props, this.props 的值也会是 undefined.

    最关键的:

    我们组件的实例上赋予了一个 state 变量. 并让它的值是一个对象.

    state 的值, 要么是一个对象: {}, 要么是 null.

    这样, 组件便有了一个内部状态.

    使用状态

    在类的任何地方, 我们都可以通过组件实例拿到这个状态并使用.

    比如在 render() 方法里面:

    render(){
    
      let { count } = this.state;
    
      return (
        <div>
          <p>目前计数: {count}</p>
          <button>计数 +1</button>
        </div>
      )
    }
    
    

    我们访问了 this.state.count 的值, 并渲染了它.

    改变状态

    现在我们想做一件事情, 点击按钮, count 的值就 +1.

    我们需要先给按钮添加一个点击事件:

    render(){
    
        let { count } = this.state;
    
        return (
          <div>
            <p>目前计数: {count}</p>
            <button
              onClick={()=>{
                this.setState({
                  count: count +1
                })
              }}
            >计数 +1</button>
          </div>
        )
      }
    

    现在你点击按钮, 就会发现数字会出现变化.

    这里有一些关键点:

    添加事件我们会在后面详细说, 现在简单说一下, 给元素一个 onClick 的属性, 就添加了点击事件, 事件接收一个回调函数.

    要想改变 count 的值, 你不能直接修改 this.state, 而应该使用组件实例的 this.setState() 接口.
    另外, 如果你的 state 属性很多, 比如:

    state= {
      count: 0,
      c1: 0,
      c2: 0
    };
    

    如果你只想改变 c1 的值, 那么只需:

    this.setState({
      c1: 2
    })
    

    就可以了.

    何为内部状态

    如果你渲染多个 Counter 的实例:

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

    点击不同的按钮, 查看界面:

    image.png

    你会发现, 组件实例之间的状态互不影响. 这也是为什么我们把组件的 state 称为内部状态.


    State 的重要特性

    本节的内容说的比较抽象. 你需要使用代码调试来理解.

    State 会合并更新

    比如你的状态是这样的:

    this.state= {
      c1: 0,
      c2: 0
    };
    

    你可以这样去只更新其中的一部分:

    this.setState({
      c1: 5,
    });
    
    this.setState({
      c2: 8,
    });
    

    这里就自然地引出了 setState 的第二种使用方式:可以接受一个函数作为参数。React.js 会把上一个 setState 的结果传入这个函数,你就可以使用该结果进行运算、操作,然后返回一个对象作为更新 state 的对象:

      handleClickOnLikeButton () {
        this.setState((prevState) => {
          return { count: 0 }
        })
        this.setState((prevState) => {
          return { count: prevState.count + 1 } // 上一个 setState 的返回是 count 为 0,当前返回 1
        })
        this.setState((prevState) => {
          return { count: prevState.count + 2 } // 上一个 setState 的返回是 count 为 1,当前返回 3
        })
        // 最后的结果是 this.state.count 为 3
      }
    

    上面我们进行了三次 setState,但是实际上组件只会重新渲染一次,而不是三次;这是因为在 React.js 内部会把 JavaScript 事件循环中的消息队列的同一个消息中的 setState 都进行合并以后再重新渲染组件。

    深层的原理并不需要过多纠结,你只需要记住的是:在使用 React.js 的时候,并不需要担心多次进行 setState 会带来性能问题。

    state 通常是异步更新

    如果现在 c1 的值是 0, 然后你更新:

    // 现在 c1 = 0
    
    this.setState({
      c1: 2
    });
    
    console.log(this.state.c1) // 打印 0
    
    

    这个时候打印的结果是 0. 原因是调用 this.setState() 后, c1 的值不会立即发生更新.

    在异步执行的函数里面, state 会同步更新

    正常情况下 :更新是异步的,setState会合并更新。
    异步执行的函数里面:setState会同步更新。
    setTimeout、异步请求的回调函数(ajax),Promise

    要注意性能问题,异步是同步更新的,所以要注意一下state的效率。
    比如一个页面有多个数据需要请求,加载一个页面的时候比如有5个图表,请求数据之后渲染图表,如果是不同的接口,就要写多个请求函数,一旦这样,数据就是同步改变。

    查看如下代码:

    // 现在 c1 = 0
    
    setTimeout(()=>{
      this.setState({
        c1: 2
      });
    
      console.log(this.state.c1) // 打印 2
    })
    

    这个时候结果打印 2, 如果 this.setState() 在一个异步函数里面调用, 那么 state 会立即更新.

    以下的函数同样是这种情况:

    • Promise 的回调函数
    • ajax 响应后的回调函数

    只要是异步执行的函数, 就适用这种情况.

    this.setState() API

    setState() 第一个参数可以传入一个对象, 这种使用方式我们已经知道.

    第一个参数还可以传入一个函数:

    this.setState((preState, props)=>{
      return {
        c1: preState.c1 + 1
      }
    });
    

    preState 是之前的 state

    props 是组件的 props

    函数返回的就是要更新的组件状态.

    再看看如下代码:

    // c1 此时是 0
    
    this.setState((preState, props)=>{
      console.log(preState.c1); // 打印 0
      return {
        c1: 2
      }
    });
    
    console.log(this.state.c1); // 打印 0
    
    this.setState((preState, props)=>{
      console.log(preState.c1); // 打印 2
      return {
        c1: 5
      }
    });
    
    console.log(this.state.c1); // 打印 0
    
    

    仔细查看这几次打印, state 异步更新的情况没有变.

    preState 的值是前一次 setState() 调用之后, 得到的 State;

    第二个参数

    setState() 第二个参数是一个可选的毁掉函数, 当组件更新完成之后, 会调用.

    state 的更新只是浅层合并

    如果这是现在 state 的情况:

    this.state = {
      c1: 0,
      c2: {a:1, b: 2}
    }
    

    现在你这样更新:

    this.setState({
      c2: {a: 40}
    })
    

    这个时候, 最终的 state 会变成这样:

    this.state = {
      c1: 0,
      c2: {a:40}
    }
    

    c2b 属性不见了. 因为整个对象都会换掉了.

    setState() 之后进行 1 个层级的浅层合并.

    // setState如果直接传对象,会覆盖掉之前的,如果传callback,就可以获取之前的state
    export default class Number extends Component{
        constructor(props){
            super(props)
            this.state = {
                magicNumber : Math.random().toString().slice(2,6),
                a:{
                    m1:1,
                    m2:2
                }
            }
        }
        changeNumber=()=>{ //更新a的数据
            this.setState((prevState,props)=>{
                return {
                    a:{
                        ...prevState.a,
                        m1:2
                    }
                }
            })
            this.setState((prevState,props)=>{
                return {
                    a:{
                        ...prevState.a,
                        m2:3
                    }
                }
            })
          
        }
    }
    
    

    总结

    为了使得组件的可定制性更强,在使用组件的时候,可以在标签上加属性来传入配置参数。
    组件可以在内部通过 this.props 获取到配置参数,组件可以根据 props 的不同来确定自己的显示形态,达到可配置的效果。
    可以通过给组件添加类属性 defaultProps 来配置默认参数。
    props 一旦传入,你就不可以在组件内部对它进行修改。但是你可以通过父组件主动重新渲染的方式来传入新的 props,从而达到更新的效果。


    我们来一个关于 state 和 props 的总结。

    state 的主要作用是用于组件保存、控制、修改自己的可变状态。state 在组件内部初始化,可以被组件自身修改,而外部不能访问也不能修改。你可以认为 state 是一个局部的、只能被组件自身控制的数据源。state 中状态可以通过 this.setState 方法进行更新,setState 会导致组件的重新渲染。

    props 的主要作用是让使用该组件的父组件可以传入参数来配置该组件。它是外部传进来的配置参数,组件内部无法控制也无法修改。除非外部组件主动传入新的 props,否则组件的 props 永远保持不变。

    区别:

    state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。

    1. state 和 props 有着千丝万缕的关系。它们都可以决定组件的行为和显示形态。一个组件的 state 中的数据可以通过 props 传给子组件,一个组件可以使用外部传入的 props 来初始化自己的 state。但是它们的职责其实非常明晰分明:state 是让组件控制自己的状态,props 是让外部对组件自己进行配置。
    <ul className="listWrap">
                    {
                        this.state.datas.map((elt,i)=>{
                            return (
                                <LiDom
                                id={elt.id}
                                content={elt.content}
                                deleteValue={this.deleteValue}
                                index={i}
                                ></LiDom>
                            )
                        })
                    }
                </ul>
    class LiDom extends Component{
        render(){
          let {id,content,deleteValue,index} = this.props
           return (
                <li key={id}>
                    <span>{index+1}.{content}</span>
                    <button onClick={()=>deleteValue(id)}>删除</button>
                 </li>
            )
        }
        // 更新前执行的,所以在没有更新之前
        shouldComponentUpdate(nP,nS){
            return !this.props.content === nP.content
        }
    }
    

    如果你觉得还是搞不清 state 和 props 的使用场景,那么请记住一个简单的规则:尽量少地用 state,尽量多地用 props。

    1. 没有 state 的组件叫无状态组件(stateless component),设置了 state 的叫做有状态组件(stateful component)。因为状态会带来管理的复杂性,我们尽量多地写无状态组件,尽量少地写有状态的组件。这样会降低代码维护的难度,也会在一定程度上增强组件的可复用性。前端应用状态管理是一个复杂的问题,我们后续会继续讨论。

    React.js 非常鼓励无状态组件,在 0.14 版本引入了函数式组件——一种定义不能使用 state 组件,例如一个原来这样写的组件:

    class HelloWorld extends Component {
      constructor() {
        super()
      }
    
      sayHi () {
        alert('Hello World')
      }
    
      render () {
        return (
          <div onClick={this.sayHi.bind(this)}>Hello World</div>
        )
      }
    }
    

    用函数式组件的编写方式就是:

    const HelloWorld = (props) => {
    const sayHi = (event) => alert('Hello World')
    return (
      <div onClick={sayHi}>Hello World</div>
    )
    }
    

    以前一个组件是通过继承 Component 来构建,一个子类就是一个组件。而用函数式的组件编写方式是一个函数就是一个组件,你可以和以前一样通过 <HellWorld /> 使用该组件。不同的是,函数式组件只能接受 props 而无法像跟类组件一样可以在 constructor 里面初始化 state。你可以理解函数式组件就是一种只能接受 props 和提供 render 方法的类组件。

    相关文章

      网友评论

          本文标题:4-组件与 prop、state

          本文链接:https://www.haomeiwen.com/subject/ozjkkftx.html