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;
PS:不能在函数组件中使用ref,因为函数组件没有实例,需要使用实例应当使用下面的hook方法
此外,还有比较新的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作为变量名
PS:更多 refs
对 Dom
的操作在 Refs & DOM
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,比如:autoplay
和 autoplay={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
未完待续。。。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。