乐趣区

关于react.js:React-高级指引的学习笔记-2

Refs 转发

接上文,开始钻研 refs 转发的起因是因为,antd4 更新的 form 表单更改了 3 的数据获取和办法调用的形式,须要在应用前应用相似于 this.refForm = React.createRef() 来定义,获取的时候通过 this.refForm.current 的形式来应用 form 自带的函数和数据,于是对 refs 转发产生了趣味。

在一开始接触 ref 的时候是在 vue 外面,再 vue 外面,通过 ref 来操作子组件的函数,通过 emit 来调用父组件函数:

<template>
    <div ref='child' value="test">
</template>

methods:{testRef:function () {console.log(this.$refs.child) // 获取上文的 div 实例
    }
}

而在 react 中,根本实现相似,不过就是将结构 refs 参考实例进行了前置,须要先结构能力用,比方想要管制一个组件外部的某些实在 dom,须要留神的是,这时候的 ref 并不是通过 props 传递上来到 div 的 dom 中,而是获取了 RefButton 组件的实例,具体实现如下:

/*
* @desc 先定义一个组件式的 button,在外部咱们定义了一个简略的 click 事件,待会再外层调用它
*/
import React, {Component} from "react";
import styles from "./styles.module.less";

class RefButton extends Component {constructor(props) {super(props);
    this.state = {test: "测试用数据"};
  }
  onClick = () => {const { test} = this.state;
    this.setState({test: test + 1});
  };
  render() {const { test} = this.state;
    return (<div className={styles.contain}>
        {test}
        ........
      </div>
    );
  }
}
export default RefButton;

/*
* @desc 父组件
*/
class Refs extends Component {constructor(props) {super(props);
    this.state = {test: "新改的数据"};
    this.ref = React.createRef(); // 定义一个 ref}

  onClick = () => {const { test} = this.state;
    const message = this.ref.current; // 调用之前定义的 ref
    message.setState({test: test + 1,});
    this.setState({test: test + 1,});
  };
  render() {
    return (<div className={styles.contain}>
          <RefButton ref={this.ref} />  // 让定义的 ref 传递上来
          <button className={styles.test_button} onClick={() => this.onClick()}>
            测试 ref 按钮
          </button>
          <Parent />
        </div>
    );
  }
}

export default Refs;

此外,还有比拟新的 hook 的用法,我只写了一个简略的例子,想要看细节能够去看看《对于 useRef 的用法》,外面具体介绍了该 hook 的根本应用,demo 如下:

/*
* @desc 定义了两个函数组件,parent 和 child
*/
import React, {useImperativeHandle, useRef, useState} from "react";

// 引入 useState、useRef、useImperativeHandle 组件

// 子组件
function Child(props, ref) {const [number, setNumber] = useState(0);
  const funcref = useRef();
  // 通过应用 useImperativeHandle 来给父组件传递子组件办法,为受控组件的更新和保护进行数据传递
  useImperativeHandle(ref, () => ({
    // 定义了一个递增函数,父组件单击按钮触发子组件的 addNumber 函数,给 input 中的数加一
    addNumber(value = 1) {typeof value === "number" && setNumber(value + number);
    },
    getFocus() {funcref.current.focus();
    },
  }));
  return (
    <>
      <input type="text" ref={funcref} value={number}></input>
    </>
  );
}

// 用 forwardRef 包裹子组件,使得父组件的 ref 穿透传递上来
// 我这里只包了一层,实践上来说无论多少层,只有在外部包裹了
// 都是能够获取到 ref 的
const ForwardChild = React.forwardRef(Child); 

// 父组件
function Parent() {// let [number, setNumber] = useState(0);
  // 在应用类组件的时候,创立 ref 返回一个对象,该对象的 current 属性值为空
  // 只有当它被赋给某个元素的 ref 属性时,才会有值
  // 所以父组件(类组件)创立一个 ref 对象,而后传递给子组件(类组件),子组件外部有元素应用了
  // 那么父组件就能够操作子组件中的某个元素
  // 然而函数组件无奈接管 ref 属性 <Child ref={xxx} /> 这样是不行的
  // 所以就须要用到 forwardRef 进行转发
  const inputRef = useRef(); //{current:''}
  function getFocus() {inputRef.current.getFocus();
  }
  function addMenber() {inputRef.current.addNumber();
  }
  return (
    <>
      <div className={styles.test_parent}>
        测试函数组件
        <ForwardChild ref={inputRef} />
        <button onClick={addMenber}>+</button>
        <button onClick={getFocus}> 获取焦点 </button>
      </div>
    </>
  );
}

官网文档也说了,针对 HOC(高阶组件),refs 转发很有用,就比方我一开始说的 antd 的 form 表单,然而他并不是把 ref 作为 props 传递上来了,他只是获取了高阶组件返回的组件的实例,如果想要在组件外部应用父组件 ref 的,则能够通过 React.forwardRef() 来将 ref 传递上来,该办法能够透传。
PS:须要留神如果想要让 ref 当作 props 传递上来,不能再应用 ref 作为变量名

Fragments

我的了解就是通明层,看起来有货色,其实都是骗零碎的,让他认为这里有最外层包裹了,不会给你找麻烦,毕竟很多时候想要封装组件,必须有个最外层,而有的组件其实是并列的关系,多包一层,即便是空的 div 也是会出问题的,比如说罕用的例子:

<table>
    <tr>
        <div>
            <td></td>
            <td></td>
        </div>
    </tr>
</table>

看起来两头那层 div 是没什么货色的,然而它就会让你的 table 无奈渲染进去。这时候应用 <Fragment>{Child}</Fragment><React.Fragment>{Child}</React.Fragment> 对子组件进行包裹就能够防止这样的问题,此外 React 还提供该语法的简写 <>{Child}</> 这样的空标签也能够达到雷同的目标。

高阶组件

高阶组件就是一种比拟特地的函数,该函数以组件为参数,返回后果也是一个组件。次要是针对一些复用性较高,然而又须要有肯定的自定义操作,比方整个组件的业务逻辑统一,区别在于传入的对象不统一或者是其中一个函数不统一,如果是一般的写法,要么在组件内加很多的判断,当这种判断不是一种两种,而是极多,带有不确定的状况的时候,就须要有一个相似于中间件的货色来解决现有组件,以实现相应的逻辑,于是就有了高阶组件,集体认为是 组件的直达函数。如下是一个简略的 HOC demo,具体的流程见正文和代码:

/*
* @desc 结构了一个简略的 hoc 函数,传入组件,而后承受一个函数,扭转组件的内容
*/
function hocFunction(Comp, func) {
  class hocFunction extends React.Component {constructor(props) {super(props);
      this.state = {data: func(),
      };
    }

    componentDidUpdate(prevProps) {console.log("Current props:", this.props);
      console.log("Previous props:", prevProps);
    }

    render() {
      // 过滤掉非此 HOC 额定的 props,且不要进行透传
      const {extraProp, ...passThroughProps} = this.props;

      // 将 props 注入到被包装的组件中。// 通常为 state 的值或者实例办法。const injectedProp = this.state.data;

      return <Comp injectedProp={injectedProp} {...passThroughProps} />;
    }
  };
  
   // 这是 react 进行的约定,因为容器组件也会显示在调试中,所以尽量都要给他命名
  hocFunction.displayName = `hocFunction(${getDisplayName(hocFunction)})`;
  
  return hocFunction;
}

function HocTest(props) {
  return (
    <>
      <p>
        {props.injectedProp}:test all the hoc will be change.
      </p>
    </>
  );
}

class Hoc extends Component {constructor(props) {super(props);
    this.state = {test: "",};
    this.LogTestHoc = hocFunction(HocTest, () => {return 20;});
  }
  changeProps = () => {const { test} = this.state;
    this.setState(
      {test: test !== ""?"" : "这是一个测试用的数据",},
      () => {this.LogTestHoc = hocFunction(HocTest, () => {return test;});
      }
    );
  };
  render() {const { test} = this.state;
    const LOGHOC = this.LogTestHoc;
 
    return (<div className={styles.contain}>
        <ErrorBoundary>
          <LOGHOC test={test}></LOGHOC>
          <button id="buttonC" onClick={this.changeProps}>
            测试更新 props
          </button>
        </ErrorBoundary>
      </div>
    );
  }
}

export default Hoc;

该 demo 在 constructor 中定义 hoc,并且在单击更新按钮时触发 hoc 的扭转,从而使得 hoc 包裹的 HocTest 组件发生变化。

PS: 不能在 render 种定义 hoc,这会造成每次调用 render 函数都会创立一个新的 hoc,这将导致子树每次渲染都会进行卸载,和从新挂载的操作!

与第三方库的协同

没怎么看

深刻 JSX

官网说的很分明:

实际上,JSX 仅仅只是 React.createElement(component, props, ...children) 函数的语法糖

次要内容就是将一些 JSX 中的一些禁忌和应用办法
1、不论你用没用,只有写了就肯定要 import 引入到以后作用域
2、能够应用点语法来调用 react 自带的办法组件等,如 React.Fragment
3、如果你想应用一个组件,组件名必须 大写,定义的时候没大写,应用的时候也要通过赋值给大写的变量
4、不能在应用组件的时候进行判断,变量等,必须在应用前就扭转。如:

const components = {
  photo: PhotoStory,
  video: VideoStory
};

function Story(props) {
    // 谬误!JSX 类型不能是一个表达式。return <components[props.storyType] story={props.story} />;
}
function Story(props) {const Comp = components[props.storyType]
    // 正确!return <Comp story={props.story} />;
}

5、能够在 props 中应用表达式:value={1+2+2+3}
6、字符串字面量能够间接作为 props 传递
7、props 的默认值时 true,比方:autoplayautoplay={true} 时等价的
8、能够通过开展运算符写入 props,比方:<Demo {...props} />
9、在 JSX 中字符串、子组件、函数都能成为子元素
10、能够有多个子元素:

<Comp>
    <First />
    <Second />
</Comp>

11、能够通过 this.props.children 来获取子元素,这在定义外层容器中非常有用;

// 定义一个 Comp 的函数组件,在子组件外层包裹一个 div
function Comp(props){const {children , ...other} = props
    return <div {...other}>{Children}</div>
}

性能优化

没有怎么看

Portals

第一眼看到这个的时候,有一点惊喜的,作为一个萌新前端,之前在本人写组件的时候,就碰到过想要把组件的一部分渲染在组件的外层(已经写了一个相似于 select 的组件,开展后被内容遮挡,设置 z -index 又会让未开展的时候显示异样)。

该办法的外围就是 React.createPortal(this.props.children, this.el) 办法,该办法是承受一个子元素,将其挂载到一个元素上, 上面是一个简略的 demo,创立一个 div,将 div 挂载到 body 内,而后把子元素置于 div 中。感兴趣的能够尝试本人写一个简略的 modal,上面就是一个 madal 的一部分:

class Demo extends Component {constructor(props) {super(props);
    this.state = {visible: false};
    
    // 先创立一个 div
    this.el = document.createElement("div");
  }
  
  componentDidMount() {
    // 在组件生成时断定状态,如果是 visible = true 就调用生成子组件的函数
    if (this.props.visible !== this.state.visible) {this.initDom();
      this.setState({visible: this.props.visible});
    }
  }
  
  componentDidUpdate(preProps, nextProps) {
    // 在组件更新时再进行断定
    if (this.props.visible !== nextProps.visible) {this.initDom();
      nextProps.visible = this.props.visible;
    }
  }
  componentWillUnmount() {
    // 在组件卸载时捣毁之前创立的 div
    document.body.removeChild(this.el);
  }
  
  initDom = () => {const { destory, visible} = this.props;
    let self = this;
    // 将之前创立的 div 置入 body
    document.body.appendChild(this.el);
    // 定义了一个蒙版的底层
    this.el.setAttribute("class", "select_modal_box");
    this.el.style.cssText = `position: absolute;width: 100%;height: 100%;top:0;background:rgba(0,0,0,0.3)`;
    // 定义了一个函数监听蒙版的单击事件,接管外界的 destory 和 visible
    this.el.addEventListener(
      "click",
      function (event) {if (event.target.className === "select_modal_box") {console.log("456456465", event.target);
          if (destory) {self.props.onCancel();
            let doc = document.getElementById("select_modal_box");
            console.log(event.currentTarget);
            event.currentTarget &&
              doc &&
             document.body.removeChild(event.currentTarget);
          } else {self.props.onCancel();
            this.style.visibility = "hidden";
          }
          self.setState({visible: false,});
        }
      },
      false
    );
    // 如果 visible= false 暗藏 modal
    !visible ? (this.el.style.visibility = "hidden") : console.log("test");
  };
  render() {
    // 返回一个挂载到 div 上的子组件,子组件由内部传入。return ReactDOM.createPortal(this.props.children, this.el);
  }
}

Profiler

未完待续。。。

退出移动版