React 类组件和函数组件-props&state

元素与组件

const div = React.createElement('div',...)
复制代码

这是一个元素(d小写)

const Div = ()=>React.createElement('div'..)
复制代码

这是一个组件(D大写)

  • 什么是组件

    组件,从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

    组件可以理解为页面的一部分,可以和其他组件组合起来形成完成的页面。

    就目前而言,一个返回React元素的函数就是组件。在Vue里,一个构造选项就可以表示一个组件。

React两种组件

一、函数组件

定义组件最简单的方式就是编写 JavaScript 函数:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}
复制代码

该函数是一个有效的 React 组件,因为它接收唯一带有数据的 “props”(代表属性)对象与并返回一个 React 元素。这类组件被称为“函数组件”,因为它本质上就是 JavaScript 函数。

使用方法:<Welcome name="frank"/>

二、类组件

你同时还可以使用 ES6 的 class 来定义组件:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}
复制代码

上述两个组件在 React 里是等效的。

使用方法:<Welcome name="frank"/>

二者区别与共同点

共同点

  1. 无论是使用函数或是类来声明一个组件,它决不能修改它自己的 props。
  2. 所有 React 组件都必须是纯函数,并禁止修改其自身 props 。
  3. React是单项数据流,父组件改变了属性,那么子组件视图会更新。
  4. 属性 props 是外界传递过来的,状态 state 是组件本身的,状态可以在组件中任意修改

组件的属性和状态改变都会更新视图

区别

  1. 函数组件的性能比类组件的性能要高,因为类组件使用的时候要实例化,而函数组件直接执行函数取返回结果即可。
  2. 为了提高性能,尽量使用函数组件

<Welcome />会被翻译成什么

  • <div />翻译为React.createElement('div')
  • <Welcome />翻译为React.createElement(Welcome)
  • babel online直接翻译

image.png

React.createElement的逻辑

  • 如果传入一个字符串‘div’,则会创建一个div
  • 如果传入一个函数,则会调用该函数,获取其返回值
  • 如果传入一个,则在类前面加个new, 执行构造函数constructor,获取组件对象,然后调用对象的render方法,获取返回值

注意: 组件名称必须以大写字母开头。

React 会将以小写字母开头的组件视为原生 DOM 标签。例如,<div /> 代表 HTML 的 div 标签,而 <Welcome /> 则代表一个组件,并且需在作用域内使用 Welcome。

小试牛刀

类组件和函数组件使用 props&state

二者作用

propsstate 都是普通的 JavaScript 对象。它们都是用来保存信息的。组件内可以引用其他组件,组件之间的引用形成了一个树状结构(组件树),如果下层组件需要使用上层组件的数据或方法,上层组件就可以通过下层组件的 props 属性进行传递,因此 props 是组件对外的接口。组件除了使用上层组件传递的数据外,自身也可能需要维护管理数据,这就是组件对内的接口 state 。根据对外接口 props 和对内接口 state ,组件计算出对应界面的UI

一.props(外部数据)

props是组件对外的数据接口,只能读不能写

  • 类组件读取props属性: this.props.xxx
  • 函数组件读取props参数: props.xxx
class Son extends React.Component {
  render() {
    return (
      <div className="Son">
        我是儿子,我爸对我说「{this.props.messageForSon}」
        <Grandson messageForGrandson="孙贼你好" />
      </div>
    );
  }
}

const Grandson = props => {
  return (
    <div className="Grandson">
      我是孙子,我爸对我说「{props.messageForGrandson}」
    </div>
  );
};
复制代码

二.state (内部数据)

state 是组件对内的数据接口,能读能写

  • 类组件读取state: this.state, 写入为 this.setState 。推荐写为this.setState=()=>{****}, 因为更好理解异步的setState
    • setState 之后,state不会马上改变,立马读取state会失败,推荐使用setState(函数)
    • this.setState(this.state)不推荐,React希望我们不要修改旧state(不可变数据),常用代码setState({n:state.n+1})
  • 函数组件用 usestate 返回数组,第一项读,第二项写
    • 没有this,一律用参数和变量

image.png

函数组件使用 React.useState() 是赋值,它会返回一个新的数组,然后使用解构赋值。数组的第一个元素为赋值,第二项为修改值

class Son extends React.Component {
  constructor() {
    super();
    this.state = {
      n: 0
    };
  }
  add() {
    // this.state.n += 1 为什么不行
    //因为React没有像Vue一样监听data一样监听state
    this.setState({ n: this.state.n + 1 });
  }
  render() {
    return (
      <div className="Son">
        儿子 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>
  );
};
复制代码

复杂的state写法

类组件写法(自动合并)

class Son extends React.Component {
  constructor() {
    super();
    this.state = {
      n: 0,
      m: 0
    };
  }
  addN() {
    this.setState({ n: this.state.n + 1 });
  }
  addM() {
    this.setState({ m: this.state.m + 1 });
  }
  render() {
    return (
      <div className="Son">
        儿子 n: {this.state.n}
        <button onClick={() => this.addN()}>n+1</button>
        m: {this.state.m}
        <button onClick={() => this.addM()}>m+1</button>
        <Grandson />
      </div>
    );
  }
}
复制代码
  • 小结

    类组件的 setState 修改 state 的部分属性,另外未修改的属性会自动沿用之前的属性,二者会自动合并。值得注意的是:第一层的属性会自动合并。如果n和m里面还有属性,对象中的属性变更,第二层属性并不会自动合并,例如

   this.state = {
      n: 0,
      m: 0,
      user: {
        name: "frank",
        age: 18
      }
    };
复制代码

如果修改 user.name ,n和m会自动合并,而age不会合并会变成空

解决二层属性不会自动合并,可以使用 Object.assign 或者操作符,例如

// Ojbect.assign会新建空对象,把之前的数据复制到空对象里
 changeUser() {
    const user = Object.assign({}, this.state.user);
    user.name = "jack";
    this.setState({
      user: user
    });
  }
  
//...
changeUser() {
    this.setState({
      // m 和 n 不会被置空
      user: {
        ...this.state.user, // 复制之前的所有属性
        name: "jack"
        // age 被置空
      }
    });
复制代码

函数组件的写法(不会自动合并)

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

函数组件则完全不会自动合并,需要使用操作符 ... 进行合并,例如

const Grandson = () => {
  const [state, setState] = React.useState({
    n: 0,
    m: 0
  });
  return (
    <div className="Grandson">
      孙子 n:{state.n}
     <button onClick={() => setState({...state, n: state.n + 1 })}>n+1</button>
      m:{state.m}
     <button onClick={() => setState({...state, m:state.m + 1})</button>>
    </div>
  );
};
复制代码

三.state的注意事项(类组件)

不能直接修改State

因为组件并不会重新重发 render 。在下面的例子中,其实n已经改变了,但是UI不会自动更新,只有调用 setState 才会触发UI更新。React并不像Vue一样是数据响应式,监听到数据变化就触发视图更新

// 错误的写法
this.state = n +1
// 正确的写法
this.setState = ({n: state.n +1})
复制代码

setState是异步更新UI

调用setState,组件的state并不会立即改变,立马读取state会失败。因为setState是异步执行的,而且要把修改的状态放入一个队列中,React会优化真正的执行时机,并且React会出于性能原因,可能会将多次setState的状态修改合并成一次状态修改。需要注意的是,同样不能依赖当前的props计算下个state,因为props的更新也是异步的。推荐使用的方式是setState(fn函数)

State 的更新是浅合并

当调用setState修改组件状态时,只需要传入发生改变的状态变量,而不是组件完整的state,因为组件state的更新是一个浅合并Shallow Merge的过程。

// 例如,一个组件的state为
this.state = {
  title : 'React',
  content : 'React is an wonderful JS library!'
}

// 当只需要修改状态title时,只需要将修改后的title传给setState
this.setState({title: 'Reactjs'});

// React会合并新的title到原来的组件state中,同时保留原有的状态content,合并后的state为
{
  title : 'Reactjs',
  content : 'React is an wonderful JS library!'
}
复制代码

不推荐使用this.setState(this.state)

因为state为不可变数据immutable data,React不希望我们修改旧的state。所以推荐保留旧的数据的同时新建对象来操作state。例如 setState({n: state.n+1})

四.事件绑定

类组件的事件绑定

写法一:最安全的写法

<button onClick={() => this.addN()}>n+1</button>
//箭头函数无法改变this
// 另外一种写法,命名函数,再传入绑定事件
this._addN =()=>this.addN()
<button onClick={this._addN}>n+1</button>
复制代码

写法二: addN里面的this会指向全局变量window,不推荐

<button onClick={this.addN}>n+1</button>
复制代码

image.png
写法三:使用bind绑定当前this的新函数,很麻烦

<button onClick={this.addN.bind(this)}>n+1</button>
复制代码

写法四:最终写法,this指向正确

    constructor(){
    this.addN = ()=> this.setState({n: this.state.n + 1})
  } 
  //上面用的太多了,React就发明了下面的新语法,可以把addN直接放在外面
  addN = () => this.setState({n: this.state.n + 1});

复制代码

也就是说,最终写法为

class Son extends React.Component{
    addN = () => this.setState({n: this.state.n + 1});
    render(){
        return <button onClick={this.addN}>n+1</button>
    }
}
复制代码

写法五:错误写法,this会指向window

class Son extends React.Component{
   addN(){ 
      this.setState({n: this.state.n + 1})
    }
    // 二者写法完全相等
    addN: functioin(){
      this.setState({n: this.state.n + 1})
    }
} 
复制代码

写法四 VS 写法五

  1. 写法四的addN函数是对象本身的属性,意味着每个Son组件都可有自己的addN函数,例如,两个Son,每个都会有自己的addN函数。因为this指向Son组件
  2. 写法五的addN函数是对象的共有属性,也就是原型上的属性,即prototype里面。意味着所有的Son组件共用一个addN函数。因为this指向全局变量window
  3. 所有函数的this都是参数,由调用决定,所以可变
  4. 箭头函数的this不变,因为箭头函数不接受this

引出this面试题

答案详见链接

面试题一

var a = {
   name:" 里面的name",
   sayName:function(){
       console.log("this.name = " + this.name);
  }
};

var name = "外面的name";
function sayName(){
    var sss = a.sayName;
    sss(); //this.name = ?
    a.sayName(); //this.name = ?
    (a.sayName)(); //this.name = ?
    (b = a.sayName)();//this.name = ?
}
sayName()
复制代码

面试题二

var length = 10;
function fn() {
    console.log(this.length);
}
var obj = {
   length: 5,
   method: function(fn) {
     fn();
     arguments[0]();//第一个参数,其实就是fn()
   }
};

obj.method(fn, 1)
复制代码

五.Props VS state

  • props 是传递给组件的(类似于函数的形参),而 state 是在组件内被组件自己管理的(类似于在一个函数内声明的变量)
  • props 是组件的只读属性,组件内部不能直接修改 props ,要想修改 props ,只能在该组件的上层组件中修改
  • state 是在组件中创建的,一般在 constructor 中初始化 state
  • state是可变的,是组件内部维护的一组用于反映组件UI变化的状态集合
  • state 是多变的、可以修改,每次 setState异步更新

Vue VS React(规则与自由)

相同点

  • 都是对视图的封装,React是用类和函数表示一个组件,而Vue是通过构造选项构造一个组件
  • 都提供了 createElement 的XML简写,React提供的是JSX语法,而Vue提供的是模板写法,语法很多

不同点

  • React是把HTML放在JS里写(HTML in JS),Vue是把JS放在HTML里写(JS in HTML)

总结:

  • Vue: 能把你做的,都帮你做了
  • React: 能不做的,我都不做
  • vue:把JS放在HTML里写,JS in HTML
  • React: 把HTML放在JS里写,HTML in JS

image.png

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享