react摘要

基础

  • jsx(原理?)
  • 实现一个简单的createElement及render方法
  • 组件声明方式
  • props和state的区别
  • 绑定事件(一些特殊的事件也可以往上冒泡,如onBlur)
  • 属性校验
  • setState的使用(有两种写法)
  • 复合组件(多个组件进行组合,父子通信、子父通信的实现)
  • 受控与非受控组件

jsx表达式的用法

  1. 可以放JS的执行结果
  2. 如果换行需要用()包裹jsx代码
  3. 可以把JSX元素当作函数的返回值
  4. <{来判断是表达式还是js

jsx属性

在JSX中分为普通属性和特殊属性,像class要写成className,for要写成htmlFor style要采用对象的方式, dangerouslyInnerHTML插入html

组件声明方式

两种,普通函数及class

属性校验

通常这个是会写在一个component组件中,提供给别人使用。

prop-types

在react中props是组件对外暴露的接口,但通常组件并不会明显的申明他会暴露那些接口及类型,这不太利于组件的复用,但比较好的是React提供了PropTypes这个对象用于校验属性的类型,PropTypes包含组件属性的所有可能类型,以下我们通过一个示列来说明(对象的key是组件的属性名,value是对应属性类型)组件属性的校验

class Person extends Component {
    // 传的props格式不对,不会中断页面渲染
    static propTypes = {
        name: PropTypes.string.isRequired,
        age: PropTypes.number,
        gender: PropTypes.oneOf(['男', '女']),
        hobby: PropTypes.array,
       // 自定义类型
        salary: function (props, key, com) {
            if (props[key] < 1000) {
                throw new Error(`${com} error ${props[key]} is too low`)
            }
        },
        position: PropTypes.shape({
            x: PropTypes.number,
            y: PropTypes.number
        })
    }
    constructor(props) {
        super();
    }
    render() {
        let { name, age, gender, hobby, salary, position } = this.props;
        return (<div>
            {name}{age}
            {gender}{hobby}
            {salary} {JSON.stringify(position)}
        </div>)
    }
}

受控与非受控组件

一般受控或非受控组件,指的是表单元素,如input、select、checkbox等。

受控是指表单的值,必须通过事件+状态来改变,比如说:

<input value="123" />

在不加onChange事件的前提下,我们是没法将123改为其他值的。

当有多个表单元素时,不同的元素onChange事件可以通过event.target.name来做区分(前提是每个元素需要加上name的属性)

非受控,是指不需要通过状态来改变,上面的代码可以改为:

<input defaultValue="123" />

那么它在表单提交时,怎么来获取元素值呢?答案是通过ref

ref的写法

字符串

// 在某个方法中 this.refs.username // jsx<input ref="username"  />stylus

函数

// 在某个方法中 this.username // jsx<input ref={ref => this.username=ref}  />verilog

对象

// 在constructor里面this.username = React.createRef();// 在某个方法中this.username.current  // 这个就是dom元素// jsx<input ref={this.username} />kotlin

第一种写法现在不是太推荐了,一般使用第二种或者第三种,第三种需要v16.3以上的版本。

生命周期

在网上找到一张图,还是挺直观的:


import React, { Component } from 'react';
class Counter extends React.Component {
    static defaultProps = {
        name: 'zpu'
    };
    constructor(props) {
        super();
        this.state = { number: 0 }
        console.log('1.constructor构造函数')
    }
    componentWillMount() {
        console.log('2.组件将要加载 componentWillMount');
    }
    componentDidMount() {
        console.log('4.组件挂载完成 componentDidMount');
    }
    handleClick = () => {
        this.setState({ number: this.state.number + 1 });
    };
    shouldComponentUpdate(nextProps, nextState) { // 代表的是下一次的属性 和 下一次的状态
        console.log('5.组件是否更新 shouldComponentUpdate');
        return nextState.number % 2;
    }
    componentWillUpdate() {
        console.log('6.组件将要更新 componentWillUpdate');
    }
    componentDidUpdate() {
        console.log('7.组件完成更新 componentDidUpdate');
    }
    render() {
        console.log('3.render');
        return (
            <div>
                <p>{this.state.number}</p>
                {this.state.number > 3 ? null : <ChildCounter n={this.state.number} />}
                <button onClick={this.handleClick}>+</button>
            </div>
        )
    }
}
class ChildCounter extends Component {
    componentWillUnmount() {
        console.log('组件将要卸载componentWillUnmount')
    }
    componentWillMount() {
        console.log('child componentWillMount')
    }
    render() {
        console.log('child-render')
        return (<div>
            {this.props.n}
        </div>)
    }
    componentDidMount() {
        console.log('child componentDidMount')
    }
    componentWillReceiveProps(newProps) { // 第一次不会执行,之后属性更新时才会执行
        console.log('child componentWillReceiveProps')
    }
    shouldComponentUpdate(nextProps, nextState) {
        console.log('child shouldComponentUpdate');
        return nextProps.n % 3; // 子组件判断接收的属性 是否满足更新条件 为true则更新
    }
}
export default Counter;
// defaultProps
// constructor
// componentWillMount
// render
// componentDidMount
// 状态更新会触发的
// shouldComponentUpdate nextProps,nextState=>boolean
// componentWillUpdate
// componentDidUpdate
// 属性更新
// componentWillReceiveProps newProps
// 卸载
// componentWillUnmount

关于React v16.3 新生命周期

到了react16.3,生命周期去掉了以下三个:

  • componentWillMount
  • componentWillReceiveProps
  • componentWillUpdate

This lifecycle was previously named componentWillMount. That name will continue to work until version 17. Use the rename-unsafe-lifecycles codemod to automatically update your components.

上面像componentWillMountcomponentWillUpdate去掉一般影响不会太大,但是像componentWillReceiveProps这个就有关系了,所以react又新增了两个生命周期:

  • static getDerivedStateFromProps
  • getSnapshotBeforeUpdate
    getDerivedStateFromProps
    getDerivedStateFromProps就是用来替代componentWillReceiveProps方法的,但是需要注意是它是静态方法,在里面无法使用this,它会返回一个对象作为新的state,返回null则说明不需要更新state。
class Example extends React.Component {
  static getDerivedStateFromProps(nextProps, prevState) {
    // 没错,这是一个static
  }
}

另外它的触发时机是:在组件构建之后(虚拟dom之后,实际dom挂载之前) ,以及每次获取新的props之后。和之前componentWillReceiveProps有一个区别是,后者只有获取新的props之后,才会触发,第一次是不触发的。

简单例子如下:

if (nextProps.currentRow !== prevState.lastRow) {
  return {
    ...
    lastRow: nextProps.currentRow,
  };
  // 不更新state
  return null
}
getSnapshotBeforeUpdate

文档的意思大概是使组件能够在可能更改之前从DOM中捕获一些信息(例如滚动位置)。简单地说就是在更新前记录原来的dom节点属性,然后传给componentDidUpdate。

componentDidCatch

我们都知道如果组件中有错误,那整个页面可能就会变成空白,然后控制台一堆红色报错。

在 React 16.x 版本中,引入了所谓 Error Boundary 的概念,从而保证了发生在 UI 层的错误不会连锁导致整个应用程序崩溃;未被任何异常边界捕获的异常可能会导致整个 React 组件树被卸载。

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class ErrorBoundary extends Component {
    constructor(props) {
        super(props);
        this.state={hasError:false};
    }
    componentDidCatch(err,info) {
        this.setState({hasError: true});
    }
    render() {
        if (this.state.hasError) {
            return <h1>Something Went Wrong</h1>
        }
        return this.props.children;
    }
}
class Clock extends Component {
    render() {
        return (
            <div>hello{null.toString()}</div>
        )
    }
}
class Page extends Component {
    render() {
        return (
            <ErrorBoundary>
                <Clock/>
            </ErrorBoundary>
        )
    }
}
ReactDOM.render(<Page/>,document.querySelector('#root'));

上下文(context api)

传统写法

父组件:

static childContextTypes={
    color: PropTypes.string,
    changeColor:PropTypes.func
}
getChildContext() {
    return {
        color: this.state.color,
        changeColor:(color)=>{
            this.setState({color})
        }
    }
}

简单地说,就是写两个东西:

声明context类型
声明context对象,即子组件需要的context
子组件:

static contextTypes = {
    color: PropTypes.string,
    changeColor: PropTypes.func
}  
// 使用
this.context.color;

也是先要声明类型(这里特指需要用到的context类型,如果不需要用的话,就不需要声明),然后使用this.context来取,就OK了。。

新式写法

上面的传统写法,其实是有点问题的:如果某个组件shouldComponentUpdate返回了false后面的组件就不会更新了。

当然这个我在团队中提及,他们有些人觉得scu返回了false,应该是不让它去更新了,但我觉得是需要更新的。

来看看写法吧。

// 创建一个消费者和提供者
let { Consumer,Provider} = React.createContext();
class Parent extends Component {
    render() {
        // Provider通过value来传递数据
        return (
            <Provider value={{ a: 1, b: 2 }}>
                <Son></Son>
            </Provider>
        );
    }
}
class Son extends Component {
    render() {
        // Consumer的children是一个函数,函数的参数为Provider的value对象
        return (
            <Consumer>
                {
                    ({a, b}) => {
                        return (
                            <div>{a}, {b}</div>
                        )
                    }
                }
            </Consumer>
        )
    }
}

写法上,比传统的写法更加舒服一些。当然实际应用中,可能会有多个Provider、多个Consumer,然后嵌套,不过这样层级写多了,比较恶心

插槽(Portals)

在react16中,提供了一个方法:
ReactDOM.createPortal(child, container)
React16.0中发布了很多新特性,我们来看portal,React提供了一个顶级API—portal,用来将子节点渲染到父节点之外的dom节点

Portals 提供了一种很好的将子节点渲染到父组件以外的 DOM 节点的方式。
  
  ReactDOM.createPortal(child, container)
  
  第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或碎片。第二个参数(container)则是一个 DOM 元素。
  
  From React文档
import React from "react";
import { createPortal } from "react-dom";
import classnames from "classnames";
const rootEle = document.body;

/**
 * show: 这个属性通过切换类名改变样式控制组件控制弹层的出现/隐藏
 * onSwitch: 通过传递函数,给予弹出层自我切换的方法
 * children: react组件自带属性,获取组件的开始和结束标记之间的内容
 */

export default ({ show, onSwitch, children }) =>
  createPortal(
    <div
      className={classnames("modal", { "modal-show": show })}
      onClick={onSwitch}
    >
      {children}
    </div>,
    rootEle
  );

调用,在应用中创建一个show状态来管理弹出层的切换,以及switchModal方法用来对该状态进行切换,并将这两个属性传递给弹出层组件

import React from "react";
import ReactDOM from "react-dom";
import Modal from "./Modal";
import "./styles.css";

class App extends React.PureComponent {
  state = {
    show: false
  };
  switchModal = () => this.setState({ show: !this.state.show });
  render() {
    const { show } = this.state;
    return (
      <div id="App">
        <h1 onClick={this.switchModal}>点我弹出</h1>
        <Modal show={show} onSwitch={this.switchModal}>
          点击关闭
        </Modal>
      </div>
    );
  }
}

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

片段(fragments)

React 中一个常见模式是为一个组件返回多个元素。 片段(fragments) 可以让你将子元素列表添加到一个分组中,并且不会在DOM中增加额外节点。

举个例子,比如说我们将一个h2元素和h3元素放到root节点去,很早之前的做法是必须要在h2和h3元素外面套一层div。但现在不一定非要这么做:

<React.Fragment>
       <h2></h2>
       <h3></h3>
</<React.Fragment>

这个也可以用在多个li返回上面,当然多个li的时候,也可以返回一个数组,加上不同的key即可,如:

const items = [
    <li key="1">1</li>,
    <li key="2">2</li>,
    <li key="3">3</li>
]
ReactDOM.render((
    <React.Fragment>
        <div>aaa</div>
        <h3>bb</h3>
        {items}
    </React.Fragment>
), document.getElementById('root'));

高阶组件(HOC)

HOC,全称: Higher-Order Components。简单地说,就是对原有的component再包装一层,有点类似extend。

HOC(High Order Component) 是 react 中对组件逻辑复用部分进行抽离的高级技术,但HOC并不是一个 React API 。 它只是一种设计模式,类似于装饰器模式。
具体而言,HOC就是一个函数,且该函数接受一个组件作为参数,并返回一个新组件。
从结果论来说,HOC相当于 Vue 中的 mixins(混合) 。其实 React 之前的策略也是采用 mixins ,但是后来 facebook 意识到 mixins 产生的问题要比带来的价值大,所以移除了 mixins

Why ? 为什么使用HOC

import React, { Component } from 'react'

class Page1 extends Component{
  componentWillMount(){
    let data = localStorage.getItem('data')
    this.setState({ data })
  }

  render() {
    return (
      <h2>{this.state.data}</h2>
    )
  }
} 

export default Page1

这个例子中在组件挂载前需要在 localStorage 中取出 data 的值,但当其他组件也需要从 localStorage 中取出同样的数据进行展示的话,每个组件都需要重新写一遍 componentWillMount 的代码,那就会显得非常冗余。那么在 Vue 中通常我们采用:
mixins: []
但是在 React 中我们需要采用HOC模式咯

import React, { Component } from 'react'

const withStorage = WrappedComponent => {
  return class extends Component{
    componentWillMount() {
      let data = localStorage.getItem('data')
      this.setState({ data })
    }

    render() {
      return <WrappedComponent data={this.state.data} {...this.props} /> 
    }
  }
}

export default withStorage

当我们构建好一个HOC之后,我们使用的时候就简单多了,还看最开始的例子,我们就不需要写 componentWillMount 了。

import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

class Page1 extends Component{
  render() {
    return <h2>{this.props.data}</h2>
  }
}

export default withStorage(Page1)

很明显,这是一个装饰器模式,那么就可以使用ES7形式

import React, { Component } from 'react'
import withStorage from '@/utils/withStorage'

@withStorage
class Page1 extends Component{
  render() {
    return <h2>{this.props.data}</h2>
  }
}

export default Page1

欢迎大家补充!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容