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,比方: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
未完待续。。。