React是前端三大框架之一,在开发中也是一项技能;这里从实际开发中总结了React开发的一些技巧,适合React初学或者有一定项目经验的同学。
1、组件通讯
1.1道具
子组件
import React from "react";
import PropTypes from "prop-types";
import { Button } from "antd";
export default class EightteenChildOne extends React.Component {
static propTypes = { //propTypes校验传入类型,详情在技巧11
name: PropTypes.string
};
click = () => {
// 通过触发方法子传父
this.props.eightteenChildOneToFather("这是 props 改变父元素的值");
};
render() {
return (
这是通过 props 传入的值{this.props.name}
);
}
}
父组件
this.eightteenChildOneToFather(mode)}>
// 或者
传传值时:
传统写法
const {dataOne,dataTwo,dataThree} = this.state
升级写法
1.2道具升级版
原理:子组件里面利用props获取父组件方法直接调用,从而改变父组件的值
注意:此方法和props大同小异,都是props的应用,所以在源码中没有模仿
调用父组件方法改变该值
// 父组件
state = {
count: {}
}
changeParentState = obj => {
this.setState(obj);
}
// 子组件
onClick = () => {
this.props.changeParentState({ count: 2 });
}
1.3提供者,消费者和上下文
1.Context在16.x之前是定义一个类别的对象,类似vue的eventBus,如果组件要使用到该值直接通过this.context获取
//根组件
class MessageList extends React.Component {
getChildContext() {
return {color: "purple",text: "item text"};
}
render() {
const {messages} = this.props || {}
const children = messages && messages.map((message) =>
);
return {children};
}
}
MessageList.childContextTypes = {
color: React.PropTypes.string
text: React.PropTypes.string
};
//中间组件
class Message extends React.Component {
render() {
return (
);
}
}
//孙组件(接收组件)
class MessageItem extends React.Component {
render() {
return (
{this.context.text}
);
}
}
MessageItem.contextTypes = {
text: React.PropTypes.string //React.PropTypes在 15.5 版本被废弃,看项目实际的 React 版本
};
class Button extends React.Component {
render() {
return (
);
}
}
Button.contextTypes = {
color: React.PropTypes.string
};
2.16.x之后的上下文使用了提供商和客户模式,在某些提供商的初始值,在子孙级的消费者中获取该值,并且能够传递函数,修改了上下文声明了一个上下文的定义,上下文.js
import React from 'react'
let { Consumer, Provider } = React.createContext();//创建 context 并暴露Consumer和Provider模式
export {
Consumer,
Provider
}
父组件导入
// 导入 Provider
import {Provider} from "../../utils/context"
父组件定义的值:{name}
子组件
// 导入Consumer
import { Consumer } from "../../utils/context"
function Son(props) {
return (
//Consumer容器,可以拿到上文传递下来的name属性,并可以展示对应的值
{name => (
// 在 Consumer 中可以直接通过 name 获取父组件的值
子组件。获取父组件的值:{name}
)}
);
}
export default Son;
1.4 EventEmitter
EventEmiter传送门 使用事件插件定义一个类别的事件机制
1.5路由传参
1.参数
xxx
this.props.history.push({pathname:"/path/" + name});
读取参数用:this.props.match.params.name
2.查询
this.props.history.push({pathname:"/query",query: { name : 'sunny' }});
读取参数用: this.props.location.query.name
3.状态
this.props.history.push({pathname:"/sort ",state : { name : 'sunny' }});
读取参数用: this.props.location.query.state
4.搜索
xxx
this.props.history.push({pathname:`/web/search?id ${row.id}`});
读取参数用: this.props.location.search
这个在react-router-dom:v4.2.2有bug,传参替换页面会空白,刷新才会加载出来
5.优缺点
1.params在HashRouter和BrowserRouter路由中刷新页面参数都不会丢失
2.state在BrowserRouter中刷新页面参数不会丢失,在HashRouter路由中刷新页面会丢失
3.query:在HashRouter和BrowserRouter路由中刷新页面参数都会丢失
4.query和 state 可以传对象
1.6 onRef
原理:onRef通讯原理就是通过props的事件机制将组件的this(组件实例)当做参数传到父组件,父组件就可以操作子组件的状态和方法
jsx
export default class EightteenChildFour extends React.Component {
state={
name:'这是组件EightteenChildFour的name 值'
}
componentDidMount(){
this.props.onRef(this)
console.log(this) // ->将EightteenChildFour传递给父组件this.props.onRef()方法
}
click = () => {
this.setState({name:'这是组件click 方法改变EightteenChildFour改变的name 值'})
};
render() {
return (
{this.state.name}
);
}
}
seven.jsx
eightteenChildFourRef = (ref)=>{
console.log('eightteenChildFour的Ref值为')
// 获取的 ref 里面包括整个组件实例
console.log(ref)
// 调用子组件方法
ref.click()
}
1.7参考
原理:就是通过React的ref属性获取到整个子组件实例,再进行操作
.jsx
// 常用的组件定义方法
export default class EightteenChildFive extends React.Component {
state={
name:'这是组件EightteenChildFive的name 值'
}
click = () => {
this.setState({name:'这是组件click 方法改变EightteenChildFive改变的name 值'})
};
render() {
return (
{this.state.name}
);
}
}
seven.jsx
// 钩子获取实例
componentDidMount(){
console.log('eightteenChildFive的Ref值为')
// 获取的 ref 里面包括整个组件实例,同样可以拿到子组件的实例
console.log(this.refs["eightteenChildFiveRef"])
}
// 组件定义 ref 属性
1.8 Redux
redux是一个独立的事件通讯插件,这里就不做过多的叙述
1.9 MobX
MobX也是一个独立的事件通讯插件,这里就不做过多的叙述
1.10通量
flux也是一个独立的事件通讯插件,这里就不做过多的叙述
1.11挂钩
1.hooks是利用userReducer和context实现通讯,下面模拟实现一个简单的redux
2.核心文件分为action,reducer,键入
action.js
import * as Types from './types';
export const onChangeCount = count => ({
type: Types.EXAMPLE_TEST,
count: count + 1
})
reducer.js
import * as Types from "./types";
export const defaultState = {
count: 0
};
export default (state, action) => {
switch (action.type) {
case Types.EXAMPLE_TEST:
return {
...state,
count: action.count
};
default: {
return state;
}
}
};
types.js
export const EXAMPLE_TEST = 'EXAMPLE_TEST';
18.jsx
export const ExampleContext = React.createContext(null);//创建createContext上下文
// 定义组件
function ReducerCom() {
const [exampleState, exampleDispatch] = useReducer(example, defaultState);
return (
);
}
EightteenChildThree.jsx //组件
import React, { useEffect, useContext } from 'react';
import {Button} from 'antd'
import {onChangeCount} from '../../pages/TwoTen/store/action';
import { ExampleContext } from '../../pages/TwoTen/eighteen';
const Example = () => {
const exampleContext = useContext(ExampleContext);
useEffect(() => { // 监听变化
console.log('变化执行啦')
}, [exampleContext.exampleState.count]);
return (
值为{exampleContext.exampleState.count}
)
}
export default Example;
3.hooks实际上就是对常设React的API进行了封装,暴露比较方便使用的钩子;
4.钩子有:
5,使用即时方法
function FancyInput(props, ref) {
const inputRef = useRef();
useImperativeMethods(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
return ;
}
FancyInput = forwardRef(FancyInput);
1.12插槽
slot就是将父组件的标签传给子组件,类似vue的v-slot
场景:一些组件只是共享部分dom逻辑,里面有部分逻辑是独立的
// 父组件文件
import SlotChild from 'SlotChild'
<SlotChild
slot={这是父组件的 slot}>
// 子组件
子组件直接获取 this.props.slot 就可获取到内容
1.13对比
redux,mobx和flux比较
2、require.context()
这个是webpack的api,这个在vue技巧中有介绍,因为Vue和React工程都是基于webpack打包,所以在react也可以使用
const path = require('path')
const files = require.context('@/components/home', false, /\.vue$/)
const modules = {}
files.keys().forEach(key => {
const name = path.basename(key, '.vue')
modules[name] = files(key).default || files(key)
})
3、装潢
定义:decorator是ES7的一个新特性,可以修改class的属性
import React from 'react'
import Test from '../../utils/decorators'
@Test
//只要Decorator后面是Class,默认就已经把Class当成参数隐形传进Decorator了。
class TwentyNine extends React.Component{
componentDidMount(){
console.log(this,'decorator.js') // 这里的this是类的一个实例
console.log(this.testable)
}
render(){
return (
这是技巧23
)
}
}
export default TwentyNine
decorators.js
function testable(target) {
console.log(target)
target.isTestable = true;
target.prototype.getDate = ()=>{
console.log( new Date() )
}
}
export default testable
很多中间件,像redux里面就封装了Decorator的使用
4、使用if ... else
场景:有些时候需要根据不同状态值页面显示不同内容
import React from "react";
export default class Four extends React.Component {
state = {
count: 1
};
render() {
let info
if(this.state.count===0){
info=(
这是数量为 0 显示
)
} else if(this.state.count===1){
info=(
这是数量为 1 显示
)
}
return (
{info}
);
}
}
5、state值改变的五种方式
方式1
export default class EightteenChildFour extends React.Component { state={ name:'这是组件EightteenChildFour的name 值' } componentDidMount(){ this.props.onRef(this) console.log(this) // ->将EightteenChildFour传递给父组件this.props.onRef()方法 } click = () => { this.setState({name:'这是组件click 方法改变EightteenChildFour改变的name 值'}) }; render() { return ( {this.state.name} ); }}
方式2:callBack
this.setState(({count})=>({count:count+2}))
方式3:接收状态和道具参数
this.setState((state, props) => {
return { count: state.count + props.step };
});
方式4:钩
const [count, setCount] = useState(0)
// 设置值
setCount(count+2)
方式5:state值改变后调用
this.setState(
{count:3},()=>{
//得到结果做某种事
}
)
6、监听状态变化
1.16.x之前使用componentWillReceiveProps
componentWillReceiveProps (nextProps){
if(this.props.visible !== nextProps.visible){
//props 值改变做的事
}
}
注意:有些时候componentWillReceiveProps在props值未变化也会触发,因为在生命周期的第一次渲染后不会被调用,但是会在之后的每次渲染中被调用=当父组件再次传送props
2.16.x之后使用getDerivedStateFromProps,16.x以后componentWillReveiveProps也未删除
export default class Six extends React.Component {
state = {
countOne:1,
changeFlag:''
};
clickOne(){
let {countOne} = this.state
this.setState({countOne:countOne+1})
};
static getDerivedStateFromProps (nextProps){
console.log('变化执行')
return{
changeFlag:'state 值变化执行'
}
}
render() {
const {countOne,changeFlag} = this.state
return (
countOne 值为{countOne}
{changeFlag}
);
}
}
7、组件定义方法
方式1:ES5的功能定义
function FunCom(props){
return 这是Function 定义的组件
}
ReactDOM.render( , mountNode)
// 在 hooks 未出来之前,这个是定义无状态组件的方法,现在有了 hooks 也可以处理状态
方式2:ES5的createClass定义
const CreateClassCom = React.createClass({
render: function() {
return 这是React.createClass定义的组件
}
});
方式3:ES6的扩展
class Com extends React.Component {
render(){
return(这是React.Component定义的组件)
}
}
召唤
export default class Seven extends React.Component {
render() {
return (
);
}
}
ES5的createClass是利用功能模拟类的写法做出来的es6; 通过es6添加类的属性创建的组件此组件创建简单。
8、通过ref属性获取component
方式1:也是初始的用法,通过this.refs [属性名获取]也可以作用到组件上,从而拿到组件实例
class RefOne extends React.Component{
componentDidMount() {
this.refs['box'].innerHTML='这是 div 盒子,通过 ref 获取'
}
render(){
return(
)
}
}
方式2:通过函数,在dom例程或组件上挂载函数,函数的入参是dom例程或组件实例,达到效果与字符串形式是一样的,都是获取其引用
class RefTwo extends React.Component{
componentDidMount() {
this.input.value='这是输入框默认值';
this.input.focus();
}
render(){
return(
{ this.input = comp; }}/>
)
}
}
方式3:React.createRef()React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom例程或组件上,该ref的当前属性,将能拿到dom例程或组件的实例
class RefThree extends React.Component{
constructor(props){
super(props);
this.myRef=React.createRef();
}
componentDidMount(){
console.log(this.myRef.current);
}
render(){
return
}
}
方式4:React.forwardRef
React 16.3版本后提供的,可以建立创建子组件,以传递ref
class RefFour extends React.Component{
constructor(props){
super(props);
this.myFourRef=React.forwardRef();
}
componentDidMount(){
console.log(this.myFourRef.current);
}
render(){
return
}
}
子组件通过React.forwardRef来创建,可以将ref传递到内部的例程或组件,并且实现跨层级的引用。forwardRef在高阶组件中可以获取到原始组件的实例。这个功能在技巧18会着重讲
9、static使用
场景:声明静态方法的关键字,静态方法是指即使没有组件实例也可以直接调用
export default class Nine extends React.Component {
static update(data) {
console.log('静态方法调用执行啦')
}
render() {
return (
这是 static 关键字技能
);
}
}
Nine.update('2')
注意:1.ES6的类,我们定义一个组件的时候通常是定义了一个类,而static则是创建了一个属于这个类的属性或方法
2。组件则是这个类的一个实例,component的props和状态是属于这个实例的,因此实例仍未创建3..static
静态反应定义的,而加上static关键字,就表示该方法不会被实例继承,而是直接通过类来调用,所以也是无法访问到此
4.getDerivedStateFromProps也通过静态方法监听值,详情请见技巧6
10、构造函数和超级
回顾:
1.谈这两个属性之前,先回顾一下ES6函数定义方法
2.每一个使用类方式定义的类的都有一个构造函数,这个函数是构造函数的主函数,该函数体内部的this指向生成的实例
3.super关键字用于访问和调用一个对象的父对象上的函数
export default class Ten extends React.Component {
constructor() { // class 的主函数
super() // React.Component.prototype.constructor.call(this),其实就是拿到父类的属性和方法
this.state = {
arr:[]
}
}
render() {
return (
这是技巧 10
);
}
}
11、属性类型
场景:检测预定子组件的数据类型
类型检查PropTypes自反应v15.5起已弃用,请使用prop-types
方式1:旧的写法
class PropTypeOne extends React.Component {
render() {
return (
{this.props.email}
{this.props.name}
);
}
}
PropTypeOne.propTypes = {
name: PropTypes.string, //值可为array,bool,func,number,object,symbol
email: function(props, propName, componentName) { //自定义校验
if (
!/^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(.[a-zA-Z0-9_-])+/.test(
props[propName]
)
) {
return new Error(
"组件" + componentName + "里的属性" + propName + "不符合邮箱的格式"
);
}
},
};
方法2:利用ES7的静态属性关键字static
class PropTypeTwo extends React.Component {
static propTypes = {
name:PropTypes.string
};
render() {
return (
{this.props.name}
);
}
}
12、使用类分类声明语法
场景:可以在不使用构造函数的情况下初始化本地状态,并通过使用箭头函数声明类方法,而无需另外对它们进行绑定
class Counter extends Component {
state = { value: 0 };
handleIncrement = () => {
this.setState(prevState => ({
value: prevState.value + 1
}));
};
handleDecrement = () => {
this.setState(prevState => ({
value: prevState.value - 1
}));
};
render() {
return (
{this.state.value}
)
}
}
13、初步组件
1.场景:路由切换,如果同步加载多个页面路由会导致缓慢
2.核心API:
加载程序:需要加载的组件
加载:未加载出来的页面展示组件
延迟:延迟加载时间
超时:超时时间
3.使用方法:
安装react-loadable,语法动态导入。react-loadable是通过webpack的异步导入实现的
const Loading = () => {
return loading;
};
const LoadableComponent = Loadable({
loader: () => import("../../components/TwoTen/thirteen"),
loading: Loading
});
export default class Thirteen extends React.Component {
render() {
return ;
}
}
4.Loadable.Map()
并行加载多个资源的高阶组件
14、动态组件
场景:做一个tab切换时就会涉及到组件动态加载
重置是利用三元表达式判断组件是否显示
class FourteenChildOne extends React.Component {
render() {
return 这是动态组件 1;
}
}
class FourteenChildTwo extends React.Component {
render() {
return 这是动态组件 2;
}
}
export default class Fourteen extends React.Component {
state={
oneShowFlag:true
}
tab=()=>{
this.setState({oneShowFlag:!this.state.oneShowFlag})
}
render() {
const {oneShowFlag} = this.state
return (
{oneShowFlag? : }
);
}
}
如果是单个组件是否显示可以用短路运算
oneShowFlag&&
15、递归组件
场景:tree组件
利用React.Fragment或div封装循环
class Item extends React.Component {
render() {
const list = this.props.children || [];
return (
{list.map((item, index) => {
return (
{item.name}
{// 当该节点还有children时,则递归调用本身
item.children && item.children.length ? (
- {item.children}
) : null}
);
})}
);
}
}
16、惯性组件和不惯性组件
保守组件:组件的状态通过React的状态值state或props控制
class Controll extends React.Component {
constructor() {
super();
this.state = { value: "这是受控组件默认值" };
}
render() {
return {this.state.value};
}
}
不适用组件:组件不被React的状态值控制,通过dom的特性或者React的ref来控制
class NoControll extends React.Component {
render() {
return {this.props.value};
}
}
简介代码:
export default class Sixteen extends React.Component {
componentDidMount() {
console.log("ref 获取的不受控组件值为", this.refs["noControll"]);
}
render() {
return (
);
}
}
17.高阶组件(HOC)
17.1定义
1.就是类似高阶函数的定义,将组件作为参数或返回一个组件的组件;
2.作用:
捕获重复代码,实现组件合并,常见场景,页面
分解;条件渲染,控制组件的渲染逻辑(渲染劫持),常见场景,权限控制;
捕获/劫持被处理组件的生命周期,常见场景,组件渲染性能追踪,日志打点
17.2实现方法
1.属性代理
import React,{Component} from 'react';
const Seventeen = WrappedComponent =>
class extends React.Component {
render() {
const props = {
...this.props,
name: "这是高阶组件"
};
return ;
}
};
class WrappedComponent extends React.Component {
state={
baseName:'这是基础组件'
}
render() {
const {baseName} = this.state
const {name} = this.props
return
基础组件值为{baseName}
通过高阶组件属性代理的得到的值为{name}
}
}
export default Seventeen(WrappedComponent)
2.反向继承
原理就是利用超级改变改组件的这个方向,继而就可以在该组件处理容器组件的一些值
const Seventeen = (WrappedComponent)=>{
return class extends WrappedComponent {
componentDidMount() {
this.setState({baseName:'这是通过反向继承修改后的基础组件名称'})
}
render(){
return super.render();
}
}
}
class WrappedComponent extends React.Component {
state={
baseName:'这是基础组件'
}
render() {
const {baseName} = this.state
return
基础组件值为{baseName}
}
}
export default Seventeen(WrappedComponent);
18、元素是否显示
一般用三元表达式
flag?显示内容:''
flag&&显示内容
19、Dialog组件创建
对话框应该是用的比较多的组件,下面有多种不同的创建方法方式1:通过状态控制组件是否显示
class NineteenChildOne extends React.Component {
render() {
const Dialog = () => 这是弹层1;
return this.props.dialogOneFlag && ;
}
}
方式2:通过ReactDom.render创建播放层-挂载根路由器外层
通过原生的createElement,appendChild,removeChild和react的ReactDOM.render,
ReactDOM.unmountComponentAtNode来控制元素的显示和隐藏
NineteenChild.jsx
import ReactDOM from "react-dom";
class Dialog {
constructor(name) {
this.div = document.createElement("div");
this.div.style.width = "200px";
this.div.style.height = "200px";
this.div.style.backgroundColor = "green";
this.div.style.position = "absolute";
this.div.style.top = "200px";
this.div.style.left = "400px";
this.div.id = "dialog-box";
}
show(children) {
// 销毁
const dom = document.querySelector("#dialog-box");
if(!dom){ //兼容多次点击
// 显示
document.body.appendChild(this.div);
ReactDOM.render(children, this.div);
}
}
destroy() {
// 销毁
const dom = document.querySelector("#dialog-box");
if(dom){//兼容多次点击
ReactDOM.unmountComponentAtNode(this.div);
dom.parentNode.removeChild(dom);
}
}
}
export default {
show: function(children) {
new Dialog().show(children);
},
hide: function() {
new Dialog().destroy();
}
};
nineteen.jsx
twoSubmit=()=>{
Dialog.show('这是弹层2')
}
twoCancel=()=>{
Dialog.hide()
}
20、React.memo
作用:当类组件的输入属性相同时,可以使用pureComponent或shouldComponentUpdate来避免组件的渲染。现在,你可以通过将函数组件包装在React.memo中来实现相同的功能
import React from "react";
function areEqual(prevProps, nextProps) {
/*
如果把 nextProps 传入 render 方法的返回结果与
将 prevProps 传入 render 方法的返回结果一致则返回 true,
否则返回 false
*/
if (prevProps.val === nextProps.val) {
return true;
} else {
return false;
}
}
// React.memo()两个参数,第一个是纯函数,第二个是比较函数
export default React.memo(function twentyChild(props) {
console.log("MemoSon rendered : " + Date.now());
return {props.val};
}, areEqual);
21、React.PureComponent
作用:
1.React.PureComponent和React.Component类似,是定义一个组件类
。2.不同是React.Component没有实现shouldComponentUpdate(),而React.PureComponent通过道具和状态的浅比较实现了
。3.React .PureComponent是作用在类中,而React.memo是作用在函数中。
4.如果组件的道具和状态相同时,呈现的内容也一致,那么就可以使用React.PureComponent了,这样可以提高组件的性能
class TwentyOneChild extends React.PureComponent{ //组件直接继承React.PureComponent
render() {
return {this.props.name}
}
}
export default class TwentyOne extends React.Component{
render(){
return (
)
}
}
22、反应组件
作用:是基于ES6类的React组件,React允许定义一个类或功能作为组件,那么定义一个组件类,就需要继承React.Component
export default class TwentyTwo extends React.Component{ //组件定义方法
render(){
return (
这是技巧22
)
}
}
23、在JSX打印falsy值
定义:
1.falsy值(虚值)是在布尔值上下文中识别为false的值;
2.值有0,“”,“,``,null,undefined,NaN
export default class TwentyThree extends React.Component{
state={myVariable:null}
render(){
return (
{String(this.state.myVariable)}
)
}
}
虚值如果直接展示,会发生隐式转换,为false,所以页面不显示
24、ReactDOM.createPortal
作用:组件的渲染函数返回的元素会被挂载在它的父级组件上,createPortal提供了一种将子节点渲染到存在于父组件之外的DOM优秀的方案
import React from "react";
import ReactDOM from "react-dom";
import {Button} from "antd"
const modalRoot = document.body;
class Modal extends React.Component {
constructor(props) {
super(props);
this.el = document.createElement("div");
this.el.style.width = "200px";
this.el.style.height = "200px";
this.el.style.backgroundColor = "green";
this.el.style.position = "absolute";
this.el.style.top = "200px";
this.el.style.left = "400px";
}
componentDidMount() {
modalRoot.appendChild(this.el);
}
componentWillUnmount() {
modalRoot.removeChild(this.el);
}
render() {
return ReactDOM.createPortal(this.props.children, this.el);
}
}
function Child() {
return (
这个是通过ReactDOM.createPortal创建的内容
);
}
export default class TwentyFour extends React.Component {
constructor(props) {
super(props);
this.state = { clicks: 0 };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
clicks: prevState.clicks + 1
}));
}
render() {
return (
点击次数: {this.state.clicks}
);
}
}
这样元素就追加到指定的元素下面啦
25、在React使用innerHTML
场景:有些后台返回是html格式,就需要用到innerHTML属性
export default class TwentyFive extends React.Component {
render() {
return (
<div dangerouslySetInnerHTML={{ __html: "这是渲染的 HTML 内容" }}>