React系列二:组件

197 阅读8分钟

2:React 组件

    2.1:React 之 函数 组件

        2.1.1:函数组件 之 state 和 props

        2.1.2:函数组件 之 生命周期

    2.2:React 之 class 组件

        2.2.1:class 组件 之 state 和 props

        2.2.2:class 组件 之 生命周期

2:React 组件

组件可被拆分为不同的功能片段,这些片段可以在其他组件中使用。组件可以返回其他组件、数组、字符串和数字。

React 组件允许将 UI 拆分为独立可复用的代码片段,并对每个片段进行独立构思。从概念上类似于 JavaScript 函数。它接受任意的入参(即 “props”),并返回用于描述页面展示内容的 React 元素。

React 组件是可复用的小的代码片段,它们返回要在页面中渲染的 React 元素。

React 的组件可以定义为 class 或函数的形式。如下所示。 需要注意的是: 组件名称必须以大写字母开头。React 会将以小写字母开头的组件视为原生 DOM 标签。例如,

代表 HTML 的 div 标签,而 则代表一个组件,并且需在作用域内使用 Welcome。

2.1:React 之函数组件

React 组件的最简版本是,一个返回 React 元素的普通 JavaScript 函数,即函数形式:

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

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

在函数组件中,外部数据是通过参数 props 传递的,但没有 state 和生命周期。React v16.8.0 推出了 Hooks API,其中的 useState 这个 API 可以解决了 state 问题,useEffect 解决了生命周期问题。函数组件中的 Hooks,使得目前的函数组件功能更加丰富。在后面的第 3 部分将详细介绍 React 的常用 Hooks。

2.1.1:函数组件 之 state 和 props

声明一个函数,它就是组件。可以用函数声明,也可以用函数表达式的方法。使用 useState 去读写 state,使用 props 去获取外部数据

function Welcome(props){
  let [n,setn] = useState(10);//使用useState初始化了一个变量n,值为10。且使用setn修改n。
//也可以将n加10的操作写成一个函数
  let addTen(){
    setn(n+a10);
  };
  return <div>
  <h1>Hello,{props.name}</h1>//使用参数props去接收外面的传参
  <span>{n}</span}
  <button onClick={setn(n+10)}>给n加10</button>//使用setn修改n的值
  //使用addTen方法给n加10
  <button onClick={addTen}>给n加10</button>//使用setn修改n的值
  </div>
}
//使用方法:
<Welcome name="Jack"></Welcome>

2.1.2:函数组件 之 生命周期

使用 useEffect 去模拟 class 组件的生命周期

函数组件执行的时候,就相当于 constructor。shouldComponentUpdate 这个生命周期可以用后面的 React.memo 和 useMemo 可以解决。函数组件的返回值就是 render 的返回值。

1:模拟 componentDidMount

useEffect(()=>{console.log("第一次渲染")},[])

2:模拟 componentDidUpdate

useEffect(()=>{console.log("任意属性改变")}) useEffect(()=>{console.log("n 变了")},[n])

3:模拟 componentWillUnmount

useEffect(()=>{ console.log("第一次渲染"); return ()=>{ console.log("组件要死了"); }})

但现在有个问题,在 class 组件中使用 componentDidUpdate 这个钩子时,首次渲染是不会执行的,但是我们在函数组件中用 useEffect 去模拟该生命周期的时候,首次渲染是会执行的。这个问题怎么解决?可以自定义 Hooks,这个在第三部分详细讲解。

2.2:React 之 class 组件

也可以使用 ES6 的 class 编写,即 class 形式:

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

如需定义 class 组件,需要继承 React.Component,且在 React.Component 的子类中有个必须定义的 render() 函数。

2.2.1:class 组件之 state 和 props

在 class 组件中,添加 state(内部数据),添加 props(外部数据),需要在 constructor 中进行初始化,如下图给组件 B 传递 props:

//声明了一个父组件Parent
class Parent extends React.component{
  constructor(){
    this.state = {name:'parent',n:100};
  }//如果要初始化数据,即Parent组件里要有一个对象{name:'parent-component'},就要在costructor这里初始化
  onClick2 = ()=>{this.setState((state)=>({n:state.n+1}))};
  onclick3 = ()=>{this.state.n +=1;this.setState(this.state)}
  onClick= ()=>{console.log("这里纯属测试")};
  render(){
    return (<div onClick={onClick2}>hi,this is {this.state.name}。
    <Son name={this.state.name} onClick1={this.onClick} >hihihi</Son>
    </div>);
  }
}
//声明了一个子组件Son
class Son extends React.component{
  constructor(props){
    super(props);
    this.state = {name:'son'};
  }
  render(){
    return (<div onClick={this.props.onClick1}>hi,this is {this.state.name},my father is {this.props.name}
    <div>{this.state.children}</div>
    </div>);
  }
}

在上面 Son 组件中,通过在 constructor 中 super(props)来初始化接收外部数据。通过 this.props.XXX 来读取。但是注意 props 由于是外部传进来的,因此理论上是不允许进行修改的。这需要自己写代码的时候规范。

在 constructor 中初始化 state,this.state 后面要跟对象。对 state 的读写,读用 this.state,写使用 this.setState(newState,fn),注意 setState 不会立刻改变 this.state,会在当前代码运行完后,再去更新 this.state,从而触发 UI 更新。this.setState((state,props)=>newState,fn)这种方式的 state 更容易理解,fn 会在写入成功后执行。

请注意 parent 组件中的 onClick2 和 onClick3 函数。一般是将一个新的 state 传递给 setState,但是如果将原来的 state 传进去,也是可以运行的。即 onClick3 也是可以的,但原则上并不提倡这样做。(在函数组件中,这样的话,React 会认为该对象没有变,是没有效果的,具体可以看下面函数组件的分析)

在 parent 组件中,state 里既有 name,又有 n,但是在设置点击事件改变 state 的时候,onClick2 函数中只传了 n,没有传 name,React 是会默认 shallow merge,自动将新 state 和旧 state 进行一级合并。(只会进行一级合并,但是在函数组件中,一级合并也不会,具体可以看下面函数组件的分析)

2.2.2:class 组件之 生命周期

生命周期钩子:所谓钩子其实就是一个函数,在特定的时间被调用。

挂载:当组件实例被创建并插入 DOM 中时,其主要的生命周期调用顺序如下:constructor()-->render()-->componentDidMount()。 注意:下述生命周期方法即将过时,在新代码中应该避免使用它们:UNSAFE_componentWillMount()

更新:当组件的 props 或 state 发生变化时会触发更新。组件更新的主要的生命周期调用顺序如下:shouldComponentUpdate()-->render()-->componentDidUpdate() 当 shouldComponent 这个生命周期钩子 return false 的时候,不会调用 render 和 componentDidUpdate,后续可能会有改变,详细的可去 React 官网查询。 注意:下述方法即将过时,在新代码中应该避免使用它们: UNSAFE_componentWillUpdate()UNSAFE_componentWillReceiveProps()

卸载:当组件从 DOM 中移除时会调用如下方法:componentWillUnmount()

常见的生命周期方法介绍:

1:constructor() 用途:初始化 props,初始化 state,但此处不能调用 setState,用来写 bind this

constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
  this.handleClick = this.handleClick.bind(this);
}
//还可以用新的语法:
constructor(props) {
  super(props);
  // 不要在这里调用 this.setState()
  this.state = { counter: 0 };
}
  this.handleClick = ()=> {};

如果不初始化 state 或不进行方法绑定,则不需要为 React 组件实现构造函数。

2:render()

用途:展示视图。只能有一个根元素,如果有多个根元素的话,要用 React.Fragment 包起来,该标签可以缩写成<></>。render 里面可以写如 if-else,?:表达式,数组.map 循环等任意的 js 语法表达式。 render() 方法是 class 组件中唯一必须实现的方法。 当 shouldComponent 这个生命周期钩子 return false 的时候,不会调用 render。

3:componentDidMount()

用途:在元素插入页面后执行代码,这些代码依赖 DOM。比如如果想要获取元素的高度,最好在这里写。官方推荐在此处发起加载数据的 AJAX 请求。首次渲染会执行此钩子。这里也适合添加订阅,如果添加了订阅,请不要忘记在 componentWillUnmount() 里取消订阅。

4:componentWillReceiveProps() 当组件接收新的 props 时,会触发此钩子。该钩子已经被弃用,更名为 UNSAFE_componentWillReceiveProps。该钩子之前是推荐使用的,这里简单介绍一下。

5:ShouldComponentUpdate()和 pureComponent 组件

用途:根据 shouldComponentUpdate() 的返回值,判断 React 组件的输出是否受当前 state 或 props 更改的影响。默认行为是 state 每次发生变化组件都会重新渲染。

当 props 或 state 发生变化时,shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。

首次渲染或使用 forceUpdate() 时不会调用该方法。此方法仅作为性能优化的方式而存在。不要企图依靠此方法来“阻止”渲染,因为这可能会产生 bug。

你应该考虑使用内置的 PureComponent 组件,而不是手动编写 shouldComponentUpdate()。PureComponent 会对 props 和 state 进行浅层比较,并减少了跳过必要更新的可能性。

6:componentDidUpdate() 用途:在视图更新后执行代码。此处也可以发起 AJAX 请求,用于更新数据。首次渲染不会执行此钩子。在此处 setState 可能会引起无限循环,除非放在 if 里。若 shouldComponentUpdate 返回 false,则不触发此钩子。

7:componentWillUnmount()

用途:组件将要被移出页面然后被销毁时执行代码。unmount 过的组件不会再次 mount。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在 componentDidMount() 中创建的订阅等。componentWillUnmount() 中不应调用 setState(),因为该组件将永远不会重新渲染。组件实例卸载后,将永远不会再挂载它。