美文网首页
React 学习笔记 03 事件 + 组件

React 学习笔记 03 事件 + 组件

作者: Upcccz | 来源:发表于2019-04-13 01:44 被阅读0次

    事件

    绑定事件
    import React,{Component} from 'react'
    import ReactDOM from 'react-dom'
    
    class Hello extends Component {
        constructor(){
            super()
            this.state = {}
        }
        // 绑定事件需要使用驼峰的方式
        render(){ 
            return (
                <div onClick={ function(){console.log('ok')}}>
                    Test
                    <button onClick={this.myClick}>按钮1</button>
                    <button onClick={()=> this.show('传参')}>按钮2</button>
                </div>
            )
        }
    
        // <button onClick={this.myClick()}></button> 
        // 如果加小括号 等于直接调用 不需要点击的
        // 因为render也在生命周期中 使用这个组件的时候 就相当于创建了一个实例 
        // 这个组件实例必然就会调用render函数 必然就会解析return 的虚拟dom
        // 在解析这个应该存放变量的地方的时候 这个函数没有return值 但是这行必然会随着解析而执行
        // 所以这么写 不点击也会触发 
        myClick(){
            console.log('ok2')
        }
    
        show = (arg1)=>{
            console.log(arg1) // '传参'
        }
        // show这种绑定事件的方法最常用 在jsx必须使用箭头函数
    }
    
    事件绑定2

    也可以使用bind返回一个函数,用变量接受,在虚拟dom中直接this.xxx

    class Parent extends React.Component {
        constructor(props) {
            super(props);
            // handleClick 当做函数指针
            this.handleClick = this.handleClick.bind(this);
        }
        render(){
            return <div onClick={this.handleClick}></div>
        }
    
        handleClick(){
            console.log('111')
        }
    }
    
    使用this.setState修改state上的值
    import React,{Component} from 'react'
    import ReactDOM from 'react-dom'
    
    class Hello extends Component {
        constructor(){
            super()
            this.state = {
                msg:"哈哈哈",
                num: 1
            }
        }
        render(){ 
            return (
                <div>
                    <button onClick={()=> this.show()}>按钮2</button>
                    <p> { this.state.msg }</p>
                </div>
            )
        }
    
        show = () => {
            // 不能直接赋值来修改(不会立即渲染视图) 应该使用setState方法
            // this.state.msg = '你好'
    
            this.setState({msg:"你好"}) 
            // 只会修改对应的状态msg  并不会影响到state中的num属性 
    
            console.log(this.state.msg) // '哈哈哈'
            // 因为 this.setState() 是异步更新DOM的 console语句会率先执行完毕
    
            // 如果想立即使用修改后的值 应该使用setState的第二个参数:回调函数
            // 这个回调函数修改完成后 被调用 == vue.$nextTick()
            this.setState({msg:"show"},function(){
                console.log(this.state.msg) // show
            }) 
        }
    }
    
    React的数据绑定
    // React 默认只有单向数据流 没有像v-model那样的双向数据绑定
    // React中 如果将表单元素中的value与state中的数据绑定
    // 当状态(state中的数据)变化的时候 --> 会触发页面的更新
    // 但是 在页面触发改变的时候(文本框输入的时候) 不会同步state中的数据更新
    // 需要程序员绑定onChange事件拿到最新的文本框中的值 
    // 手动调用this.setState()去更新state中的数据
    
    import React,{Component} from 'react'
    import ReactDOM from 'react-dom'
    
    class Hello extends Component {
        constructor(){
            super()
            this.state = {
                msg:"哈哈哈",
            }
        }
        render(){ 
            return (
                <div>
                    <input type="text" value={this.state.msg} onChange={(e)=>this.changeValue(e)} ref="txt"/>
                    <input type="text" ref={el=>this.myInput = el}/>
                </div>
            )
        }
        // 设置ref为字符串是过时了的API 应该设置为一个回调函数
    
        changeValue = (e)=> {
            this.setState({
                msg: e.target.value 
            })
            // 或者使用ref属性暴露dom元素
            // this.setState({
            //     msg: this.refs.txt.value / this.myInput
            // })
        }
    }
    
    React组件的生命周期
    // 生命周期函数
    // 组件创建阶段 : 只执行一次
    componentWillMount(){}
    render(){}
    componentDidMount(){}
    
    // 组件运行阶段 : 根据状态的改变触发0或多次
    componentWillReceiveProps(){}
    shouldComponentUpdate(){}
    componentWillUpdate(){}
    render(){}
    componentDidUpdate(){}
    
    // 组件销毁阶段 : 只执行一次
    componentWillUnmount(){}
    

    流程
    1.创建时期,第一个执行的函数是constructor(),定义state
    2.组件挂载之前触发componentWillMount,此时页面还未渲染。
    3.执行render函数,创建虚拟dom,将虚拟dom挂载到真实dom上去
    4.完成挂载时,触发componentDidMount函数,页面已经被渲染。

    5.1 如果属性(props)改变,接受的传参改变了,触发componentWillReceiveProps函数,然后触发shouldComponentUpdate函数
    5.2 如果状态(state)改变,直接触发shouldComponentUpdate函数

    6.1shouldComponentUpdate函数如果return false 重新回到运行中的状态,页面没有任何更新
    6.2shouldComponentUpdate函数如果return true 即代表页面即将发生更新,继续走下面的流程,不定义默认返回true

    7.组件更新前,触发componentWillUpdate函数。
    8.执行render函数,比较更新虚拟dom,重新渲染页面
    9.组件完成更新后,触发componentDidUpdate函数

    10.组件被销毁之前触发componentWillUnmount函数,调用ReactDOM.unmountComponentAtNode(document.getElementById('app'))可以销毁一个组件。

    注意
    1.this.forceUpdate()强制刷新会跳过shouldComponentUpdate钩子函数
    2.父组件只要调用setState函数 子组件也会更新 不管子组件有没有使用到父组件中的数据 但是可以在子组件中的shouldComponentUpdate进行拦截
    3.因为父组件状态改变了 如果没有在shouldComponentUpdate中比较拦截 就会走下面的流程 必然会再次触发render函数
    4.触发了render函数 子组件就会触发更新 componentWillReceiveProps钩子就会触发
    5.PureComponent可以避免这一点,如果子组件都没有用到父组件中的数据 那么子组件比对state必然没有改变
    6.不要在shouldComponentUpdate、componentWillUpdate、render、componentDidUpdate函数中调用setState就会造成死循环

    PureComponent

    根组件在setState的时候会触发render函数,也会同时触发所有组件的更新,这是不愿意看到的,也会影响性能。

    父组件有时候的更新状态是希望子组件更新的,有时候是不希望的(没有修改影响子组件的状态的时候),所以需要在shouldComponentUpdate中具体分析

    class myCom extends React.PureComponent {
    
    }
    
    // PureComponent 与 Component 的差异 体现在shouldComponentUpdate上
    
    // PureComponent 会将nextState 和 nextProps (shouldComponentUpdate的参数)
    // 与当前的 state 和 props 去比较 如果不同 则返回true 如果相同就返回false 
    // 而Component的 shouldComponentUpdate 默认返回true
    
    // 但是  PureComponent 也只比较 state 和 props 的第一层直系属性
    
    // 因为 如果程序员 使用state定义了一个嵌套很深的json  此时一层层去比较就会太浪费性能
    
    父子组件通过props传值元素(使用函数)
    class ChildInput extends Component {
        render(){
            let pInput = this.props.parentIpt //2.2子组件接受父组件暴露的元素
            return (
                <input type="text" ref={el=>this.props.getChildInput(el)} />
                // 1.2子组件通过el传值给props接受到的函数 将input暴露给父组件 ref中函数的形参就是本身元素的指针el
            )
        }
    }
    
    class ParentInput extends Component {
        render(){
            // 1.1通过传递一个赋值接受ref的函数给子组件
            return (
                <div>
                    <input type="text" value="2.1 父组件把自己的dom元素暴露给子组件" ref={el=>this.myInput =el}/>
                    <ChildInput getChildInput={this.getChildInput.bind(this)} 
                        parentIpt = {this.myInput} ref={el=>this.chilidComp=el}>
                    </ChildInput>
                    <button onClick={this.setFocus.bind(this)}>点击让子组件的input聚焦</button>
                </div>
            )
            // 3.3 可以暴露这个组件给父组件 像上面的ref={el=>this.chilidComp=el}一样 但是不介意使用
            // 因为这样 父组件中的chilidComp这个变量代表了整个子组件 会占用很大的空间 性能不好
        }
    
        getChildInput(el){
            // 1.3子组件执行render的时候 就会触发传过去的getChildInput函数 把el作为参数传到父组件
            // 这样父组件就取到了子组件暴露的dom元素 然后赋值给自己的变量
            this.cInput = el;
        }
    
        setFocus(){
            this.cInput.focus()
        }
    }
    
    父子组件内容分发
    
    class Child extends Component {
        render(){
            return (
                
                <div>子组件的div
                    {this.props.children[1]} --- 这是span元素
                    {this.props.children[0]} --- 这是p元素
                    <hr/>
                    {this.props.children} ---将整个分发的内容放在这里
                </div>
            )
        }
    }
    
    class Parent extends Component {
        render(){
            return (
                <div>
                    <Child>
                        <p>父组件中的p元素</p>
                        <span>父组件中的span元素</span>
                    </Child>
                </div>
            )
        }
    
        // 上面这种写法 p/span元素是不会分发到子组件中区的 
        // 因为 这个子组件会整个代替渲染父组件中的Child组件的位置 
        // 子组件没有p/span元素 所以要在子组件中接受父组件的分发
    }
    
    
    // 或者传一个虚拟dom对象给子组件 做分发
    class Child extends Component {
        render(){
            return (
                
                <div>子组件的div
                    {this.props.headDom} --- 展示父组件给的h1
                </div>
            )
        }
    }
    
    class Parent extends Component {
        render(){
            return (
                <div>
                    <Child headDom={<h1> 给你一个h1去展示吧 </h1>}>
                    </Child>
                </div>
            )
        }
    }
    
    Context -- 用于嵌套过深的组件传值 避免一层层的传递
    // 有三个组件 A 包含 B ,B包含 C 
    // 如果A组件想传值给C组件 要使用props传给B 再传给C
    
    // 这种传值 可以使用 Context 来简化
    
    const ValContext = React.createContext('默认值')
    // 或者
    
    const {Provider,Consumer} = React.createContext('默认值')
    // 这样下面 就可以使用Provider Consumer 来充当根元素 而不是 ValContext.Provider
    
    // 创建Context组件 充当第一层级元素的根元素 并使用ValContext.Provider 供应者
    // 并充当需要接受数据的组件的根元素  并使用ValContext.Consumer 消费者
    
    class Ccom extends Component {
        render(){
            return (
                <ValContext.Consumer>
                    展示从A传下来的值 函数中的参数value就是传来的值 
                    函数返回一个虚拟dom元素 就可以展示传来的值在该组件上了
                    {value=> <p>我接受到A传来的值 : {value}</p>}
                </ValContext.Consumer>
            )
        }
    }
    
    class Bcom extends Component {
        render(){
            return (
                <Ccom></Ccom>
            )
        }
    }
    
    class Acom extends Component {
        render(){
            return (
                <ValContext.Provider value="我要传给c的值">
                    <Bcom></Bcom>
                </ValContext.Provider>
            )
        }
    }
    
    Fragments 可以聚合一个子元素列表 并不添加额外节点

    看起来就是一个空标签,类似vue中的template充当根标签 但是不增加额外节点

    render(){
        return (
            <>
                <ChildA/>
                <ChildB/>
            </>
        )
    }
    
    // 或者 因为空标签语法不接受 key 如果你要遍历的话 就会报警告 
    // React.Fragment 能够接收key 也是唯一只能接受key
    
    render(){
        return (
            {
                this.list.map(item=>(
                    <React.Fragment key={item.id}>
                        <ChildA/>
                        <ChildB/>
                    </React.Fragment>
                ))
            }
        )
    }
    
    Portals 将子节点渲染到父组件之外的节点上

    比如一个有遮罩的模态框,必然需要添加在body上。

    const bodyEl = document.getElementsByTags('body')[0]
    
    class Modal extends React.Component {
        constructor(props) {
            super(props);
            this.el = document.createElement('div');
        }
    
        componentDidMount() {
            bodyEl.appendChild(this.el);
        }
    
        componentWillUnmount() {
            bodyEl.removeChild(this.el);
        }
    
        render() {
            return ReactDOM.createPortal(
                this.props.children, // 下面的Modal中的Child
                this.el,
            );
        }
    }
    // 将模态框使用在父组件中  此时的Modal组件相当于一个中转 使用Portal则挂载在父组件上但可以不受父组件根元素div的限制
    // 然后再modal中将child(即模态框的实际内容)添加在创建的div上 再将div(模态框)添加到body上
    
    class Parent extends React.Component {
      render() {
        return (
          <div>
            <Modal>
              <Child />
            </Modal>
          </div>
        );
      }
    }
    
    function Child() { // 无状态组件 
      return (
        <div className="modal">
          <button>Click</button>
        </div>
      );
    }
    
    高阶组件

    高阶组件就是一个函数,且该函数接受一个组件作为参数,然后返回一个新的组件。相当于是一个工厂用来增加组件的,a,b组件都需要一把锤子,就将其传值给锤子工厂(高阶组件)去给a,b组件装一把锤子,这里的锤子就是公共部分,a,b都需要用到的。

    高阶组件的参数:需要混入公共功能的组件
    高阶组件的返回:已经增强过的组件

    
    class Acom extends React.Component {
      render() {
        return (
          <div>
            <p>我是a,我需要一把{this.props.msg}</p>
            <button onClick={this.props.show}>展示锤子吧!!!</button>
          </div>
        );
      }
    }
    
    class Bcom extends React.Component {
      render() {
        return (
          <div>
            <p>我是b,我也需要一把{this.props.msg}</p>
            <button onClick={this.props.show}>展示锤子吧!!!</button>
          </div>
        );
      }
    }
    
    function myHighCom(Com){
        // 匿名类 相当于匿名函数
        return class extends React.Component {
            constrcutor(props){
                super(props)
                this.state = {
                    wuqi:"锤子"
                }
            }
    
            render(){
                return <Com wuqi={this.state.wuqi} show={this.show}></Com>
            }
    
            show(){
                console.log('展示我的锤子')
            }
    
            componentDidMount(){
                console.log('我们在生命周期钩子函数里面也要做同样的事情')
            }
        }
    }
    
    const AplusCom = myHighCom(Acom)
    const BplusCom = myHighCom(Bcom)
    
    // 使用增强过的组件
    
    class Parent extends React.Component {
      render() {
        return (
          <div>
            <AplusCom/>
            <hr/>
            <BplusCom/>
          </div>
        );
      }
    }
    
    ReactDOM.render(<Parent />,document.getElementById('app'))
    

    相关文章

      网友评论

          本文标题:React 学习笔记 03 事件 + 组件

          本文链接:https://www.haomeiwen.com/subject/kkwfwqtx.html