React我的入门总结《二》

上次总结的知识已经入门的差很少了,今天继续总结第二阶段的东西,若是发觉有错请及时帮我纠正一下谢谢!

前端应用状态管理 —— 状态提高

React组件中父组件能够经过<Children state={this.state} />这种方式传递状态,而后在子组件里面能够经过this.props拿到这个状态,可是两个子组件若是须要传递状态的话,子组件之间是没法直接访问获得的。css

咱们能够将这种组件之间共享的状态交给组件最近的公共父节点保管,而后再经过props传递给另外一个子组件就好了,这样就能够在子组件之间共享数据了,把状态交给公共父组件的行为就叫状态提高,当某个状态被多个组件依赖或者影响的时候,就把该状态提高到这些组件的最近公共父组件中去管理,用 props 传递数据或者函数来管理这种依赖或行为。html

在父组件中传递一个回调函数而后子组件调用把东西传过来,而后父组件再传递给其余子组件,咱们就能够实现这种效果了。前端

<!-- index -->
class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: ''
        }
    }
    
    <!-- 回调函数 -->
    onSubString(options) {
        <!-- 回调结束设置参数 -->
        console.log(options)
        this.setState({
            message: options
        })
    }

    render() {
        return(
            <div>
                <!-- 向组件一传入一个回调函数 -->
                <Template1 onSubString={this.onSubString.bind(this)} />
                <!-- 向组件二传入组件一的参数 -->
                <Template2 message={this.state.message} />
            </div>
        )
    }
}

<!-- template1 -->
class Template1 extends Component {
    static defaultProps = {
        onSubString() {
            console.log('默认值')
        }
    }

    constructor(props) {
        super(props)
        this.state = {
            message: 'is template1'
        }
    }
    
    <!-- 组件挂载前调用回调函数传入参数 -->
    componentWillMount() {
        this.props.onSubString(this.state.message)
    }

    render() {
        return (
           <div className="template1">
                <h1>template1</h1>
           </div> 
        )
    }
}

<!-- template2 -->
class Template2 extends Component {
    constructor(options) {
        super(options)
    }

    render() {
        return(
            <div className="template2">
                <!-- 接受组件一的参数 -->
                <h1>这里是template2 收到 {this.props.message}</h1>
            </div>
        )
    }
}

<!-- 渲染 -->
ReactDOM.render(
    <Index />,
    document.getElementById('root')
)
    
复制代码

上面是打开浏览器展现的效果,状态提高项对于复杂的项目来讲很很差维护和管理,对于子组件之间传递参数通常都是利用Reudx来操做,也就是把一组数据共享出来全部组件均可以拿到。api

React 生命周期

对于各类具备架构能力的框架来讲生命周期是少不了的,React的生命周期跟其余框架的也差很少,首先得看看React的运行过程。数组

ReactDOM.render(
 <Index />, 
  document.getElementById('root')
)

编译为:

ReactDOM.render(
  React.createElement(Index, null), 
  document.getElementById('root')
)

复制代码

执行上面步骤时React也会执行几个步骤:浏览器

// React.createElement 中实例化一个 Index
    const Index = new Index(props, children)
    
    // React.createElement 中调用 index.render 方法渲染组件的内容
    const indexJsxObject = index.render()
    
    // ReactDOM 用渲染后的 JavaScript 对象来来构建真正的 DOM 元素
    const indexDOM = createDOMFromObject(indexJsxObject)
    
    // ReactDOM 把 DOM 元素塞到页面上
    document.getElementById('root').appendChild(indexDOM)
复制代码

咱们把 React.js 将组件渲染,而且构造 DOM 元素而后塞入页面的过程称为 组件的挂载安全

-> constructor() // 初始化
    -> componentWillMount() // 挂载前
    -> render() // 渲染
    // 而后构造 DOM 元素插入页面
    -> componentDidMount() // 挂载后
    // ...
    // 即将从页面中删除
    -> componentWillUnmount() // 删除
    // 从页面中删除
复制代码

除了上面挂载和删除的生命周期还有更新的生命周期,更新的生命周期。咱们知道setData和组件的props会给组件和另外一个组件带来从新渲染,从而会触发更新生命周期性能优化

1. componentWillUpdate() // 更新前
    2. componentDidUpdate() // 更新后
    3. componentWillReceiveProps() // 组件从父组件接收到新的 props 以前调用
复制代码

除了上面三个之外还有一个极为关键的 shouldComponentUpdate(),这个方法能够控制组件是否从新渲染。若是返回 false 组件就不会从新渲染。这个生命周期在 React.js 性能优化上很是有用。bash

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '组件更新'
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }
    
    componentWillUpdate() {
        console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新后')
    }

    render() {
        return(
            <div className="header">
                <button onClick={this.updateMessage.bind(this)}>更新数据</button>
            </div>
        )
    }
}
复制代码

上面的代码我点击三次而且我只是调用了setState并无更新message的数据,他也会每次都从新渲染,为了节约性能,咱们可使用shouldComponentUpdate()方法,shouldComponentUpdate()是重渲染时render()函数调用前被调用的函数,它接受两个参数:nextPropsnextState,分别表示下一个props和下一个state的值。而且,当函数返回false时候,阻止接下来的render()函数的调用,阻止组件重渲染,而返回true时,组件照常重渲染。架构

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '组件更新'
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }
    <!-- 调用 setState 时控制是否须要从新渲染 -->
    shouldComponentUpdate(nextProps, nextState) {
        console.log(nextState.message) // 打印的是更新前的 message
        console.log(nextProps) // 打印的是更新前的 props (这里没有 props 因此是空的对象)
        <!-- 利用上一次的 message 跟更新后的对比 若是相同则返回 false -->
        if (nextState.message == this.state.message) {
            return false
        }
    }

    componentWillUpdate() {
        console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新后')
    }

    render() {
        return (
            <div className="header">
                <button onClick={this.updateMessage.bind(this)}>更新数据</button>
            </div>
        )
    }
}
复制代码

我这里点击了4次,可是没有打印其余两个更新的生命周期,只打印了我在shouldComponentUpdate()方法里打印的东西,这说明页面并无从新渲染。

咱们再继续看props的从新渲染。组件的 state 没有变化,而且从父组件接受的 props 也没有变化,也有可能从新渲染。

<!-- Son -->
class Son extends Component {
    <!-- 点击时候把父级传过来的参数原封不动传回去 -->
    updateMessage() {
        this.props.handleClick(this.props.message)
    }

    render() {
        <!-- 看看是否从新渲染 -->
        console.log(this.props.message, 'son')
        return(
            <div>
                <button onClick={this.updateMessage.bind(this)}>{this.props.message}</button>
            </div>
        )
    }
}

<!-- Index -->

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            message: '组件更新',
        }
    }

    updateMessage() {
        this.setState({
            message: this.state.message
        })
    }

    componentWillUpdate() {
         console.log('更新前')
    }

    componentDidUpdate() {
        console.log('更新后')
    }
    
    <!-- 把子级的参数从新设置 -->
    handleClick(message) {
        this.setState({
            message: message
        })
    }

    render() {
        return (
            <div className="header">
                <Son handleClick={this.handleClick.bind(this)} message={this.state.message} />
            </div>
        )
    }
}
复制代码

我这里点击三次,不只父组件会从新渲染,连子组件都会从新渲染。咱们再来处理一下这个状况。

<!-- Son -->
shouldComponentUpdate(nextProps, nextState) {
    console.log(nextProps.message)
    if (nextProps.message === this.props.message) {
        return false
    }
}
复制代码

若是在子组件加上这个生命周期,监听父级上次传过来的props是否和此次穿过来的props是否同样,而后咱们再点击时子组件就不会从新渲染了。

这里进来时渲染打印一次,而后以后就没有打印了,以后点的两次只打印父级的更新生命周期还有子级shouldComponentUpdate()里面打印的props

React 中的 DOM 操做

通常来讲相似VueReact这种具备MVVM思想的框架通常能作到像setData同样的从新渲染,还有父子组件传参来更新状态,这些都不须要经过手动操做DOM来实现,虽然这些从新渲染机制帮助咱们免除大部分的DOM操做,可是有部分仍是不能知足,最多见的就是获取元素的宽度和高度来进行操做,或者是控制输入框的状态等等,这些都是须要依靠DOM操做来实现的。

React提供了ref属性来帮助咱们获取 已经挂载 的元素的DOM节点

class Index extends Component {
    componentDidMount() {
        console.log(this.indexBox)
    }

    render() {
        return(
            <div className="index" ref={(indexBox) => this.indexBox = indexBox} style={{width: '500px', height: '500px', backgroundColor: 'green'}}></div>
        )
    }
}
复制代码

获取到元素以后你能够调用他原有的api,好比输入框的禁止输入以及自动聚焦等等操做。

能不用 ref 就不用。特别是要避免用 ref 来作 React.js 原本就能够帮助你作到的页面自动更新的操做和事件监听。多余的 DOM 操做实际上是代码里面的“噪音”,不利于咱们理解和维护。

除此以外咱们还能够给组件加上ref

class Header extends Component {
    render(){
        return(
            <div>
                <h1>组件组件组件</h1>
            </div>
        )
    }
}

class Index extends Component {
    componentDidMount() {
        console.log(this.header)
    }

    render() {
        return(
            <div>
                <Header ref={(header)=> this.header = header } />
            </div>
        )
    }
}
复制代码

这种用法并不经常使用,也不推荐这样作。

props.children 和容器类组件

组件自己是一个不带任何内容的方形的容器,我能够在用这个组件的时候给它传入任意内容。

class Index extends Component {
    render() {
        console.log(this.props.children)
        return(
            <div className="index">
                {this.props.children}
            </div>
        )
    }
}

ReactDOM.render(
    <Index>
        <h1>我洗渣渣辉</h1>    
        <h2>我洗古天乐</h2>    
    </Index>,
    document.getElementById('root')
)
复制代码

这个属性打印出来的是一个数组,把咱们嵌套的jsx元素一个个都放到数组当中,而后经过props.children传给Index,咱们能够把它们分别存在不一样的地方。

class Index extends Component {
    render() {
        console.log(this.props.children)
        return(
            <div className="index">
                <div className="header">
                    <!-- 第0条 -->
                    {this.props.children[0]}
                </div>
                <div className="main">
                    <!-- 第1条 -->
                    {this.props.children[1]}
                </div>
            </div>
        )
    }
}
复制代码

使用自定义组件的时候,能够在其中嵌套 JSX 结构。嵌套的结构在组件内部均可以经过 props.children 获取到,这种组件编写方式在编写容器类型的组件当中很是有用。

dangerouslySetHTML 和 style 属性

出于安全考虑的缘由(XSS 攻击),在 React.js 当中全部的表达式插入的内容都会被自动转义,就至关于 jQuery 里面的 text() 函数同样,任何的 HTML 格式都会被转义掉。

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: '<h1>富文本转义</h1>'
        }
    }

    render() {
        return(
            <div className="index">
                {this.state.content}
            </div>
        )
    }
}
复制代码

若是要把<h1>转为标签,可使用dangerouslySetHTML属性动态设置元素的innerHTML

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            content: '<h1>富文本转义</h1>'
        }
    }
    render() {
        console.log(this.props.children)
        return(
            <div className="index" dangerouslySetInnerHTML={{__html: this.state.content}}></div>
        )
    }
}
复制代码

在添加了dangerouslySetHTML属性的元素里面不能再嵌套任何东西( 包括文本 ),不然将会报错。设置 innerHTML 可能会致使跨站脚本攻击(XSS),React.js 团队认为把事情搞复杂能够防止(警示)你们滥用这个属性。这个属性没必要要的状况就不要使用。


Reactstyle里面的css属性须要转化为对象才能传给元素,而且全部元素须要带-的都要转为驼峰命名,好比font-sizebackground-color,须要写为fontSizebackgroundColor才有效果。

<div className="index">
    <h1 style={{color: 'red', fontSize: 14}}>文本文本</h1>
</div>
复制代码

PropTypes 和组件参数验证

React.js 的组件实际上是为了构建大型应用程序而生。可是由于 JavaScript 这样的特性,你在编写了一个组件之后,根本不知作别人会怎么使用你的组件,往里传什么乱七八糟的参数,咱们能够利用propTypes这个库来指定传递过来参数的类型,而这个库在后面的context也要用到。

class Template1 extends Component {
    render() {
        return(
            <h1>{ this.props.userData.name }</h1>
        )
    }
}

class Index extends Component {

    constructor(props) {
        super(props)
        this.state = {
            userData: {
                name: 'jack',
                age: 18
            }
        }
    }
    
    render() {
        return(
            <div className="index">
                <Template1 userData={ this.state.userData } />
            </div>
        )
    }
}
复制代码

上面的组件不管父级传什么东西都是没有限制了,这让其余人共同开发项目很复杂,有时会由于参数问题处处找Bug,这时你能够给组件配置参数加上 类型验证

import PropTypes from 'prop-types'
class Template1 extends Component {
    static propTypes = {
        userData: PropTypes.object // 接受对象参数
    }
    
    render() {
        return(
            <h1>{ this.props.userData.name }</h1>
        )
    }
}

class Index extends Component {
    constructor(props) {
        super(props)
        this.state = {
            userData: {
                name: 'jack',
                age: 18
            }
        }
    }
    
    render() {
        return(
            <div className="index">
                <!-- 这里传递的是数值 -->
                <Template1 userData={ 123 } /> 
            </div>
        )
    }
}
复制代码

如今能够验证参数的数据类型了,若是传的类型与设置的类型不一致,将会报出警告。

propTypes提供了多种 数据验证 ,还能够限制单个元素传递,而且当参数没有传递时还能够设置默认值,若是在不传递参数下还不设置默认值,则默认为undefined,这时会出现一个很不友好的报错信息。

这时咱们可使用 isRequired 来强调这个参数必须传入。

static propTypes = {
    userData: PropTypes.object.isRequired
}
复制代码

这时报错信息里面会再添加一条提示,这样的话就更容易找到问题所在。


上一篇 --- React我的入门总结《一》

下一篇 --- React我的入门总结《三》

相关文章
相关标签/搜索