06月12, 2018

react学习笔记

最近想好好整理一下react及相关的技术栈,于是就有了这篇文章。

基础

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

jsx表达式的用法

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

jsx属性

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

组件声明方式

两种,普通函数及class

属性校验

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

prop-types

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"  />

函数

// 在某个方法中 
this.username 

// jsx
<input ref={ref => this.username=ref}  />

对象

// 在constructor里面
this.username = React.createRef();

// 在某个方法中
this.username.current  // 这个就是dom元素

// jsx
<input ref={this.username} />

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

生命周期

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

alt

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
}

所以可能需要额外写一个state来记录上一个props。

getSnapshotBeforeUpdate

getSnapshotBeforeUpdate() is invoked right before the most recently rendered output is committed to e.g. the DOM. It enables your component to capture some information from the DOM (e.g. scroll position) before it is potentially changed. Any value returned by this lifecycle will be passed as a parameter to componentDidUpdate().

文档的意思大概是使组件能够在可能更改之前从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,然后嵌套,不过这样层级写多了,比较恶心。

React@16.3 全新的Context API进阶教程

插槽(Portals)

在react16中,提供了一个方法:

ReactDOM.createPortal(child, container)

典型的案例就是Modal

function Modal({children}) {
    return ReactDOM.createPortal(children, document.querySelector('#modal-root'));
}

class Page extends Component{
    constructor() {
        super();
        this.state={show:false};
    }
    handleClick=() => {
        this.setState({show:!this.state.show});
    }
    render() {
        return (
            <div>
                <button onClick={this.handleClick}>显示模态窗口</button>
                {
                    this.state.show && (
                        <Modal>
                            <div id="modal" className="modal">
                                <div className="modal-content" id="modal-content">
                                        内容
                                        <button onClick={this.handleClick}>关闭</button>
                                </div>
                            </div>
                        </Modal>
                    )

                }
            </div>
        )
    }
}

片段(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。

比如来一个简单的需求:表单选项的默认值来自localstorage。(如果不写HOC,那么10个表单项就得写10次localstorage)

// HOC.js
import React, { Component } from 'react';

export default (name) => (WrappedComponent) => {
    class HighOrderComponent extends Component {
        constructor() {
            super();
            this.state = { data: null };
        }

        componentWillMount() {
            let data = localStorage.getItem(name);
            this.setState({ data });
        }

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

// User.js
import React from 'react';
import HOC from './HOC';

function User({data}) {
    return <input defaultValue={data} />
}

export default HOC('username')(User);

react-redux的connect,也是类似的写法。

优化性能

工具上,使用 Chrome 性能分析工具分析组件性能。

代码上,比如手动优化scu,或者借助PureComponent(这个是浅层对象的比较)。

然而在实际中,可能state会写的比较复杂,对象嵌套对象啥的,然后需要deep对象来对比,但这样又会感觉性能上比较差,这时候可以考虑使用Immutable来提升性能。

alt

Immutable 详解及 React 中实践

根据上篇文章的一些代码,就基本能在项目中实战了。不过在实际中,会使用redux-react,而connect的shouldComponentUpdate已经实现,此处无法发挥作用,除非我们使用自己写的库才行。

本文链接:www.my-fe.pub/post/react-note.html

-- EOF --

Comments

评论加载中...

注:如果长时间无法加载,请针对 disq.us | disquscdn.com | disqus.com 启用代理。