React个人入门总结《二》

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

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

React组件中父组件可以通过<Children state={this.state} />这种方式传递状态,然后在子组件里面可以通过this.props拿到这个状态,但是两个子组件如果需要传递状态的话,子组件之间是无法直接访问得到的。

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

在父组件中传递一个回调函数然后子组件调用把东西传过来,然后父组件再传递给其他子组件,我们就可以实现这种效果了。

<!-- 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来操作,也就是把一组数据共享出来所有组件都可以拿到。

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 性能优化上非常有用。

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个人入门总结《三》
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值