共计 23580 个字符,预计需要花费 59 分钟才能阅读完成。
前言
React 是前端三大框架之一, 在面试和开发中也是一项技能;
本文从实际开发中总结了 React 开发的一些技巧技巧, 适合 React 初学或者有一定项目经验的同学;
万字长文, 建议收藏。
序列文章:Vue 开发必须知道的 36 个技巧【近 1W 字】
源码地址
请戳,欢迎 star
效果图
1. 元素是否显示
一般用三元表达式
flag?<div> 显示内容 </div>:''
2. 循环元素
内部没有封装像 vue 里面 v-for 的指令, 而是通过 map 遍历
3. 绑定事件
场景: 交互就会涉及到事件点击, 然后点击选中值传参也是一个很常见场景
import React from "react"; | |
import {Button} from 'antd' | |
export default class Three extends React.Component { | |
state = { | |
flag: true, | |
flagOne: 1 | |
}; | |
click(data1,data2){console.log('data1 值为',data1) | |
console.log('data2 值为',data2) | |
} | |
render() { | |
return ( | |
<div> | |
<Button type="primary" onClick={this.click.bind(this,'参数 1','参数 2')}> 点击事件 </Button> | |
</div> | |
); | |
} | |
} |
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=(<span> 这是数量为 0 显示 </span>) | |
} else if(this.state.count===1){ | |
info=(<span> 这是数量为 1 显示 </span>) | |
} | |
return ( | |
<div> | |
{info} | |
</div> | |
); | |
} | |
} |
5.state 值改变的四种方式
方式 1
let {count} = this.state | |
this.setState({count:2}) |
方式 2:callBack
this.setState(({count})=>({count:count+2}))
方式 3: 接收 state 和 props 参数
this.setState((state, props) => {return { count: state.count + props.step}; | |
}); |
方式 4:hooks
const [count, setCount] = useState(0) | |
// 设置值 | |
setCount(count+2) |
6. 监听 states 变化
1.16.x 之前使用 componentWillReveiveProps
componentWillReceiveProps (nextProps){if(this.props.visible !== nextProps.visible){//props 值改变做的事} | |
} |
注意: 有些时候 componentWillReceiveProps 在 props 值未变化也会触发, 因为在生命周期的第一次 render 后不会被调用,但是会在之后的每次 render 中被调用 = 当父组件再次传送 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 ( | |
<div> | |
<div> | |
<Button type="primary" onClick={this.clickOne.bind(this)}> 点击加 1</Button><span>countOne 值为{countOne}</span> | |
<div>{changeFlag}</div> | |
</div> | |
</div> | |
); | |
} | |
} |
7. 组件定义方法
方式 1:ES5 的 Function 定义
function FunCom(props){return <div> 这是 Function 定义的组件 </div>} | |
ReactDOM.render(<FunCom name="Sebastian" />, mountNode) | |
// 在 hooks 未出来之前, 这个是定义无状态组件的方法, 现在有了 hooks 也可以处理状态 |
方式 2: ES5 的 createClass 定义
const CreateClassCom = React.createClass({render: function() {return <div> 这是 React.createClass 定义的组件 </div>} | |
}); |
方式 3:ES6 的 extends
class Com extends React.Component {render(){return(<div> 这是 React.Component 定义的组件 </div>) | |
} | |
} |
调用
export default class Seven extends React.Component {render() { | |
return ( | |
<div> | |
<FunCom></FunCom> | |
<Com></Com> | |
</div> | |
); | |
} | |
} |
区别: ES5 的 createClass 是利用 function 模拟 class 的写法做出来的 es6;
通过 es6 新增 class 的属性创建的组件此组件创建简单.
8. 通过 ref 属性获取 component
方式 1: 也是最早的用法, 通过 this.refs[属性名获取]
也可以作用到组件上, 从而拿到组件实例
class RefOne extends React.Component{componentDidMount() {this.refs['box'].innerHTML='这是 div 盒子, 通过 ref 获取' | |
} | |
render(){ | |
return(<div ref="box"></div>) | |
} | |
} |
方式 2: 回调函数, 在 dom 节点或组件上挂载函数,函数的入参是 dom 节点或组件实例,达到的效果与字符串形式是一样的,都是获取其引用
class RefTwo extends React.Component{componentDidMount() { | |
this.input.value='这是输入框默认值'; | |
this.input.focus();} | |
render(){ | |
return(<input ref={comp => { this.input = comp;}}/> | |
) | |
} | |
} |
方式 3:React.createRef()
React 16.3 版本后,使用此方法来创建 ref。将其赋值给一个变量,通过 ref 挂载在 dom 节点或组件上,该 ref 的 current 属性, 将能拿到 dom 节点或组件的实例
class RefThree extends React.Component{constructor(props){super(props); | |
this.myRef=React.createRef();} | |
componentDidMount(){console.log(this.myRef.current); | |
} | |
render(){return <input ref={this.myRef}/> | |
} | |
} |
方式 4:React.forwardRef
React 16.3 版本后提供的,可以用来创建子组件,以传递 ref
class RefFour extends React.Component{constructor(props){super(props); | |
this.myFourRef=React.createRef();} | |
componentDidMount(){console.log(this.myFourRef.current); | |
} | |
render(){return <Child ref={this.myFourRef}/> | |
} | |
} |
子组件通过 React.forwardRef 来创建,可以将 ref 传递到内部的节点或组件,进而实现跨层级的引用。forwardRef 在高阶组件中可以获取到原始组件的实例. 这个功能在技巧 18 会着重讲
9.static 使用
场景: 声明静态方法的关键字, 静态方法是指即使没有组件实例也可以直接调用
export default class Nine extends React.Component {static update(data) {console.log('静态方法调用执行啦') | |
} | |
render() { | |
return ( | |
<div> | |
这是 static 关键字技能 | |
</div> | |
); | |
} | |
} | |
Nine.update('2') |
注意:
1.ES6 的 class,我们定义一个组件的时候通常是定义了一个类,而 static 则是创建了一个属于这个类的属性或者方法
2. 组件则是这个类的一个实例,component 的 props 和 state 是属于这个实例的,所以实例还未创建
3. 所以 static 并不是 react 定义的,而加上 static 关键字,就表示该方法不会被实例继承,而是直接通过类来调用, 所以也是无法访问到 this
4.getDerivedStateFromProps 也是通过静态方法监听值, 详情请见技巧 6
10.constructor 和 super
回顾:
1. 谈这两个属性之前, 先回顾一下 ES6 函数定义方法
2. 每一个使用 class 方式定义的类默认都有一个 constructor 函数,这个函数是构造函数的主函数,该函数体内部的 this 指向生成的实例
3.super 关键字用于访问和调用一个对象的父对象上的函数
export default class Ten extends React.Component {constructor() { // class 的主函数 | |
super() // React.Component.prototype.constructor.call(this), 其实就是拿到父类的属性和方法 | |
this.state = {arr:[] | |
} | |
} | |
render() { | |
return ( | |
<div> | |
这是技巧 10 | |
</div> | |
); | |
} | |
} |
11.PropTypes
场景: 检测传入子组件的数据类型
类型检查 PropTypes 自 React v15.5 起已弃用,请使用 prop-types
方式 1: 旧的写法
class PropTypeOne extends React.Component {render() { | |
return ( | |
<div> | |
<div>{this.props.email}</div> | |
<div>{this.props.name}</div> | |
</div> | |
); | |
} | |
} | |
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 ( | |
<div> | |
<div>{this.props.name}</div> | |
</div> | |
); | |
} | |
} |
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 ( | |
<div> | |
{this.state.value} | |
<button onClick={this.handleIncrement}>+</button> | |
<button onClick={this.handleDecrement}>-</button> | |
</div> | |
) | |
} | |
} |
13. 异步组件
1. 场景: 路由切换, 如果同步加载多个页面路由会导致缓慢
2. 核心 API:
loader: 需要加载的组件
loading: 未加载出来的页面展示组件
delay: 延迟加载时间
timeout: 超时时间
3. 使用方法:
安装 react-loadable ,babel 插件安装 syntax-dynamic-import. react-loadable 是通过 webpack 的异步 import 实现的
const Loading = () => {return <div>loading</div>;}; | |
const LoadableComponent = Loadable({loader: () => import("../../components/TwoTen/thirteen"), | |
loading: Loading | |
}); | |
export default class Thirteen extends React.Component {render() {return <LoadableComponent></LoadableComponent>;} | |
} |
4.Loadable.Map()
并行加载多个资源的高阶组件
14. 动态组件
场景: 做一个 tab 切换时就会涉及到组件动态加载
实质上是利用三元表达式判断组件是否显示
class FourteenChildOne extends React.Component {render() {return <div> 这是动态组件 1</div>;} | |
} | |
class FourteenChildTwo extends React.Component {render() {return <div> 这是动态组件 2</div>;} | |
} | |
export default class Fourteen extends React.Component { | |
state={oneShowFlag:true} | |
tab=()=>{this.setState({oneShowFlag:!this.state.oneShowFlag}) | |
} | |
render() {const {oneShowFlag} = this.state | |
return (<div> | |
<Button type="primary" onClick={this.tab}> 显示组件{oneShowFlag?2:1}</Button> | |
{oneShowFlag?<FourteenChildOne></FourteenChildOne>:<FourteenChildTwo></FourteenChildTwo>} | |
</div>); | |
} | |
} |
15. 递归组件
场景:tree 组件
利用 React.Fragment 或者 div 包裹循环
class Item extends React.Component {render() {const list = this.props.children || []; | |
return ( | |
<div className="item"> | |
{list.map((item, index) => { | |
return (<React.Fragment key={index}> | |
<h3>{item.name}</h3> | |
{// 当该节点还有 children 时,则递归调用本身 | |
item.children && item.children.length ? (<Item>{item.children}</Item> | |
) : null} | |
</React.Fragment> | |
); | |
})} | |
</div> | |
); | |
} | |
} |
16. 受控组件和不受控组件
受控组件: 组件拥有自己的状态
class Controll extends React.Component {constructor() {super(); | |
this.state = {value: "这是受控组件默认值"}; | |
} | |
render() {return <div>{this.state.value}</div>; | |
} | |
} |
不受控组件: 组件无自己的状态, 在父组件通过 ref 来控制或者通过 props 传值
class NoControll extends React.Component {render() {return <div>{this.props.value}</div>; | |
} | |
} |
导入代码:
export default class Sixteen extends React.Component {componentDidMount() {console.log("ref 获取的不受控组件值为", this.refs["noControll"]); | |
} | |
render() { | |
return ( | |
<div> | |
<Controll></Controll> | |
<NoControll | |
value={"这是不受控组件传入值"} | |
ref="noControll" | |
></NoControll> | |
</div> | |
); | |
} | |
} |
17. 高阶组件
17.1 定义
就是类似高阶函数的定义, 将组件作为参数或者返回一个组件的组件
17.2 实现方法
1. 属性代理
import React,{Component} from 'react'; | |
const Seventeen = WraooedComponent => | |
class extends React.Component {render() { | |
const props = { | |
...this.props, | |
name: "这是高阶组件" | |
}; | |
return <WrappedComponent {...props} />; | |
} | |
}; | |
class WrappedComponent extends React.Component { | |
state={baseName:'这是基础组件'} | |
render() {const {baseName} = this.state | |
const {name} = this.props | |
return <div> | |
<div> 基础组件值为{baseName}</div> | |
<div> 通过高阶组件属性代理的得到的值为{name}</div> | |
</div> | |
} | |
} | |
export default Seventeen(WrappedComponent) |
2. 反向继承
原理就是利用 super 改变改组件的 this 方向, 继而就可以在该组件处理容器组件的一些值
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 <div> | |
<div> 基础组件值为{baseName}</div> | |
</div> | |
} | |
} | |
export default Seventeen(WrappedComponent); |
18 组件通讯
18.1 props
子组件
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 ( | |
<div> | |
<div> 这是通过 props 传入的值{this.props.name}</div> | |
<Button type="primary" onClick={this.click}> | |
点击改变父元素值 | |
</Button> | |
</div> | |
); | |
} | |
} |
父组件
<EightteenChildOne name={'props 传入的 name 值'} eightteenChildOneToFather={(mode)=>this.eightteenChildOneToFather(mode)}></EightteenChildOne>
props 传多个值时:
传统写法
const {dataOne,dataTwo,dataThree} = this.state | |
<Com dataOne={dataOne} dataTwo={dataTwo} dataThree={dataThree}> |
升级写法
<Com {...{dataOne,dataTwo,dataThree}}>
18.2 props 升级版
原理: 子组件里面利用 props 获取父组件方法直接调用, 从而改变父组件的值
注意: 此方法和 props 大同小异, 都是 props 的应用, 所以在源码中没有举例
调用父组件方法改变该值
// 父组件 | |
state = {count: {} | |
} | |
changeParentState = obj => {this.setState(obj); | |
} | |
// 子组件 | |
onClick = () => {this.props.changeParentState({ count: 2}); | |
} |
18.3 Provider,Consumer 和 Context
1.Context 在 16.x 之前是定义一个全局的对象, 类似 vue 的 eventBus, 如果组件要使用到该值直接通过 this.context 获取
// 根组件 | |
class MessageList extends React.Component {getChildContext() {return {color: "purple",text: "item text"}; | |
} | |
render() {const children = this.props.messages.map((message) => | |
<Message text={message.text} /> | |
); | |
return <div>{children}</div>; | |
} | |
} | |
MessageList.childContextTypes = { | |
color: React.PropTypes.string | |
text: React.PropTypes.string | |
}; | |
// 中间组件 | |
class Message extends React.Component {render() { | |
return ( | |
<div> | |
<MessageItem /> | |
<Button>Delete</Button> | |
</div> | |
); | |
} | |
} | |
// 孙组件(接收组件) | |
class MessageItem extends React.Component {render() { | |
return ( | |
<div> | |
{this.context.text} | |
</div> | |
); | |
} | |
} | |
MessageItem.contextTypes = {text: React.PropTypes.string //React.PropTypes 在 15.5 版本被废弃, 看项目实际的 React 版本}; | |
class Button extends React.Component {render() { | |
return (<button style={{background: this.context.color}}> | |
{this.props.children} | |
</button> | |
); | |
} | |
} | |
Button.contextTypes = {color: React.PropTypes.string}; |
2.16.x 之后的 Context 使用了 Provider 和 Customer 模式, 在顶层的 Provider 中传入 value,在子孙级的 Consumer 中获取该值,并且能够传递函数,用来修改 context
声明一个全局的 context 定义,context.js
import React from 'react' | |
let {Consumer, Provider} = React.createContext();// 创建 context 并暴露 Consumer 和 Provider 模式 | |
export { | |
Consumer, | |
Provider | |
} |
父组件导入
// 导入 Provider | |
import {Provider} from "../../utils/context" | |
<Provider value={name}> | |
<div style={{border:'1px solid red',width:'30%',margin:'50px auto',textAlign:'center'}}> | |
<p> 父组件定义的值:{name}</p> | |
<EightteenChildTwo></EightteenChildTwo> | |
</div> | |
</Provider> |
子组件
// 导入 Consumer | |
import {Consumer} from "../../utils/context" | |
function Son(props) { | |
return ( | |
//Consumer 容器, 可以拿到上文传递下来的 name 属性, 并可以展示对应的值 | |
<Consumer> | |
{name => ( | |
<div | |
style={{ | |
border: "1px solid blue", | |
width: "60%", | |
margin: "20px auto", | |
textAlign: "center" | |
}} | |
> | |
// 在 Consumer 中可以直接通过 name 获取父组件的值 | |
<p> 子组件。获取父组件的值:{name}</p> | |
</div> | |
)} | |
</Consumer> | |
); | |
} | |
export default Son; |
18.4 EventEmitter
EventEmiter 传送门
使用 events 插件定义一个全局的事件机制
18.5 路由传参
1.params
<Route path='/path/:name' component={Path}/> | |
<link to="/path/2">xxx</Link> | |
this.props.history.push({pathname:"/path/" + name}); | |
读取参数用:this.props.match.params.name |
2.query
<Route path='/query' component={Query}/> | |
<Link to={{path : '/query' , query : { name : 'sunny'}}}> | |
this.props.history.push({pathname:"/query",query: { name : 'sunny'}}); | |
读取参数用: this.props.location.query.name |
3.state
<Route path='/sort' component={Sort}/> | |
<Link to={{path : '/sort' , state : { name : 'sunny'}}}> | |
this.props.history.push({pathname:"/sort",state : { name : 'sunny'}}); | |
读取参数用: this.props.location.query.state |
4.search
<Route path='/web/search' component={Search}/> | |
<link to="web/search?id=12121212">xxx</Link> | |
this.props.history.push({pathname:`/web/search?id ${row.id}`}); | |
读取参数用: this.props.location.search |
5. 优缺点
1.params 和 search 只能传字符串, 刷新页面参数不会丢 | |
2.query 和 state 可以传对象, 但是刷新页面参数会丢失 |
18.6 onRef
原理:onRef 通讯原理就是通过 props 的事件机制将组件的 this(组件实例)当做参数传到父组件, 父组件就可以操作子组件的 state 和方法
EightteenChildFour.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 ( | |
<div> | |
<div>{this.state.name}</div> | |
<Button type="primary" onClick={this.click}> | |
点击改变组件 EightteenChildFour 的 name 值 | |
</Button> | |
</div> | |
); | |
} | |
} |
eighteen.jsx
<EightteenChildFour onRef={this.eightteenChildFourRef}></EightteenChildFour> | |
eightteenChildFourRef = (ref)=>{console.log('eightteenChildFour 的 Ref 值为') | |
// 获取的 ref 里面包括整个组件实例 | |
console.log(ref) | |
// 调用子组件方法 | |
ref.click()} |
18.7 ref
原理: 就是通过 React 的 ref 属性获取到整个子组件实例, 再进行操作
EightteenChildFive.jsx
// 常用的组件定义方法 | |
export default class EightteenChildFive extends React.Component { | |
state={name:'这是组件 EightteenChildFive 的 name 值'} | |
click = () => {this.setState({name:'这是组件 click 方法改变 EightteenChildFive 改变的 name 值'}) | |
}; | |
render() { | |
return ( | |
<div> | |
<div>{this.state.name}</div> | |
<Button type="primary" onClick={this.click}> | |
点击改变组件 EightteenChildFive 的 name 值 | |
</Button> | |
</div> | |
); | |
} | |
} |
eighteen.jsx
// 钩子获取实例 | |
componentDidMount(){console.log('eightteenChildFive 的 Ref 值为') | |
// 获取的 ref 里面包括整个组件实例, 同样可以拿到子组件的实例 | |
console.log(this.refs["eightteenChildFiveRef"]) | |
} | |
// 组件定义 ref 属性 | |
<EightteenChildFive ref="eightteenChildFiveRef"></EightteenChildFive> |
18.8 redux
redux 是一个独立的事件通讯插件, 这里就不做过多的叙述
传送门:https://www.redux.org.cn/docs…
18.9 mbox
mobox 也是一个独立的事件通讯插件, 这里就不做过多的叙述
https://cn.mobx.js.org/
18.10 flux
mobox 也是一个独立的事件通讯插件, 这里就不做过多的叙述
https://facebook.github.io/fl…
18.11 hooks
1.hooks 是利用 userReducer 和 context 实现通讯, 下面模拟实现一个简单的 redux
2. 核心文件分为 action,reducer,types
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';
eightteen.jsx
export const ExampleContext = React.createContext(null);// 创建 createContext 上下文 | |
// 定义组件 | |
function ReducerCom() {const [exampleState, exampleDispatch] = useReducer(example, defaultState); | |
return ( | |
<ExampleContext.Provider | |
value={{exampleState, dispatch: exampleDispatch}} | |
> | |
<EightteenChildThree></EightteenChildThree> | |
</ExampleContext.Provider> | |
); | |
} |
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 ( | |
<div> | |
<p> 值为{exampleContext.exampleState.count}</p> | |
<Button onClick={() => exampleContext.dispatch(onChangeCount(exampleContext.exampleState.count))}> 点击加 1</Button> | |
</div> | |
) | |
} | |
export default Example; |
3.hooks 其实就是对原有 React 的 API 进行了封装, 暴露比较方便使用的钩子;
4. 钩子有:
钩子名 | 作用 |
---|---|
useState | 初始化和设置状态 |
useEffect | componentDidMount,componentDidUpdate 和 componentWillUnmount 和结合体, 所以可以监听 useState 定义值的变化 |
useContext | 定义一个全局的对象, 类似 context |
useReducer | 可以增强函数提供类似 Redux 的功能 |
useCallback | 记忆作用, 共有两个参数,第一个参数为一个匿名函数,就是我们想要创建的函数体。第二参数为一个数组,里面的每一项是用来判断是否需要重新创建函数体的变量,如果传入的变量值保持不变,返回记忆结果。如果任何一项改变,则返回新的结果 |
useMemo | 作用和传入参数与 useCallback 一致,useCallback 返回函数,useDemo 返回值 |
useRef | 获取 ref 属性对应的 dom |
useImperativeMethods | 自定义使用 ref 时公开给父组件的实例值 |
useMutationEffect | 作用与 useEffect 相同,但在更新兄弟组件之前,它在 React 执行其 DOM 改变的同一阶段同步触发 |
useLayoutEffect | 作用与 useEffect 相同,但在所有 DOM 改变后同步触发 |
5.useImperativeMethods
function FancyInput(props, ref) {const inputRef = useRef(); | |
useImperativeMethods(ref, () => ({focus: () => {inputRef.current.focus(); | |
} | |
})); | |
return <input ref={inputRef} ... />; | |
} | |
FancyInput = forwardRef(FancyInput); |
更多 hooks 介绍请戳
18.12 对比
方法 | 优点 | 缺点 |
---|---|---|
props | 不需要引入外部插件 | 兄弟组件通讯需要建立共同父级组件, 麻烦 |
props 升级版 | 不需要引入外部插件, 子传父, 不需要在父组件用方法接收 | 同 props |
Provider,Consumer 和 Context | 不需要引入外部插件, 跨多级组件或者兄弟组件通讯利器 | 状态数据状态追踪麻烦 |
EventEmitter | 可支持兄弟, 父子组件通讯 | 要引入外部插件 |
路由传参 | 可支持兄弟组件传值, 页面简单数据传递非常方便 | 父子组件通讯无能为力 |
onRef | 可以在获取整个子组件实例, 使用简单 | 兄弟组件通讯麻烦, 官方不建议使用 |
ref | 同 onRef | 同 onRef |
redux | 建立了全局的状态管理器, 兄弟父子通讯都可解决 | 引入了外部插件 |
mobx | 建立了全局的状态管理器, 兄弟父子通讯都可解决 | 引入了外部插件 |
flux | 建立了全局的状态管理器, 兄弟父子通讯都可解决 | 引入了外部插件 |
hooks | 16.x 新的属性, 可支持兄弟, 父子组件通讯 | 需要结合 context 一起使用 |
redux , mobx 和 flux 对比
方法 | 介绍 |
---|---|
redux | 1. 核心模块:Action,Reducer,Store;2. Store 和更改逻辑是分开的;3. 只有一个 Store;4. 带有分层 reducer 的单一 Store;5. 没有调度器的概念;6. 容器组件是有联系的;7. 状态是不可改变的;8. 更多的是遵循函数式编程思想 |
mobx | 1. 核心模块:Action,Reducer,Derivation;2. 有多个 store;3. 设计更多偏向于面向对象编程和响应式编程,通常将状态包装成可观察对象,一旦状态对象变更,就能自动获得更新 |
flux | 1. 核心模块:Store,ReduceStore,Container;2. 有多个 store; |
19.Dialog 组件创建
Dialog 应该是用的比较多的组件, 下面有三种不同的创建方法
方式 1: 通过 state 控制组件是否显示
class NineteenChildOne extends React.Component {render() {const Dialog = () => <div> 这是弹层 1 </div>; | |
return this.props.dialogOneFlag && <Dialog />; | |
} | |
} |
方式 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 <div>{props.val}</div>; | |
}, areEqual); |
21.React.PureComponent
作用:
1.React.PureComponent 和 React.Component 类似,都是定义一个组件类。
2. 不同是 React.Component 没有实现 shouldComponentUpdate(),而 React.PureComponent 通过 props 和 state 的浅比较实现了。
3.React.PureComponent 是作用在类中, 而 React.memo 是作用在函数中。
4. 如果组件的 props 和 state 相同时,render 的内容也一致,那么就可以使用 React.PureComponent 了, 这样可以提高组件的性能
class TwentyOneChild extends React.PureComponent{ // 组件直接继承 React.PureComponent | |
render() {return <div>{this.props.name}</div> | |
} | |
} | |
export default class TwentyOne extends React.Component{render(){ | |
return ( | |
<div> | |
<TwentyOneChild name={'这是 React.PureComponent 的使用方法'}></TwentyOneChild> | |
</div> | |
) | |
} | |
} |
22.React.Component
作用: 是基于 ES6 class 的 React 组件,React 允许定义一个 class 或者 function 作为组件,那么定义一个组件类,就需要继承 React.Component
export default class TwentyTwo extends React.Component{ // 组件定义方法 | |
render(){ | |
return (<div> 这是技巧 22</div>) | |
} | |
} |
23. 在 JSX 打印 falsy 值
定义:
1.falsy 值 (虚值) 是在 Boolean 上下文中认定为 false 的值;
2. 值有 0,””,”,“,null,undefined,NaN
export default class TwentyThree extends React.Component{state={myVariable:null} | |
render(){ | |
return (<div>{String(this.state.myVariable)}</div> | |
) | |
} | |
} |
虚值如果直接展示, 会发生隐式转换, 为 false, 所以页面不显示
24.ReactDOM.createPortal
作用: 组件的 render 函数返回的元素会被挂载在它的父级组件上,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 ( | |
<div className="modal"> | |
这个是通过 ReactDOM.createPortal 创建的内容 | |
</div> | |
); | |
} | |
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 ( | |
<div> | |
<Button onClick={this.handleClick}> 点击加 1 </Button> | |
<p> 点击次数: {this.state.clicks}</p> | |
<Modal> | |
<Child /> | |
</Modal> | |
</div> | |
); | |
} | |
} |
这样元素就追加到指定的元素下面啦
25. 在 React 使用 innerHTML
场景: 有些后台返回是 html 格式字段, 就需要用到 innerHTML 属性
export default class TwentyFive extends React.Component {render() { | |
return (<div dangerouslySetInnerHTML={{ __html: "<span> 这是渲染的 HTML 内容 </span>"}}></div> | |
); | |
} | |
} |
为什么是危险的属性, 因为这个会导致 XSS 攻击
_html 是两个
26.React.createElement
语法:
React.createElement(
type,
[props],
[…children]
)
源码:
export default class TwentySix extends React.Component {render() { | |
return ( | |
<div> | |
{React.createElement( | |
"div", | |
{id: "one", className: "two"}, | |
React.createElement("span", { id: "spanOne"}, "这是第一个 span 标签"), | |
React.createElement("br"), | |
React.createElement("span", { id: "spanTwo"}, "这是第二个 span 标签") | |
)} | |
</div> | |
); | |
} | |
} |
原理: 实质上 JSX 的 dom 最后转化为 js 都是 React.createElement
// jsx 语法 | |
<div id='one' class='two'> | |
<span id="spanOne">this is spanOne</span> | |
<span id="spanTwo">this is spanTwo</span> | |
</div> | |
// 转化为 js | |
React.createElement( | |
"div", | |
{id: "one", class: "two"}, | |
React.createElement("span", { id: "spanOne"}, "this is spanOne"), | |
React.createElement("span", { id: "spanTwo"}, "this is spanTwo") | |
); |
27.React.cloneElement
语法:
React.cloneElement( | |
element, | |
[props], | |
[...children] | |
) |
作用: 这个方法的作用是复制组件, 给组件传值或者添加属性
核心代码
React.Children.map(children, child => { | |
return React.cloneElement(child, {count: _this.state.count}); | |
}); |
28.React.Fragment
作用:React.Fragment 可以让你聚合一个子元素列表,并且不在 DOM 中增加额外节点
核心代码
render() {const { info} = this.state; | |
return ( | |
<div> | |
{info.map((item, index) => { | |
return (<React.Fragment key={index}> | |
<div>{item.name}</div> | |
<div>{item.age}</div> | |
</React.Fragment> | |
); | |
})} | |
</div> | |
); | |
} |
29.Decorator
定义: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 (<div> 这是技巧 23</div>) | |
} | |
} | |
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 的使用
https://www.jianshu.com/p/275…
30. 给 DOM 设置和获取自定义属性
作用: 有些要通过自定义属性传值
export default class Thirty extends React.Component { | |
click = e => {console.log(e.target.getAttribute("data-row")); | |
}; | |
render() { | |
return ( | |
<div> | |
<div data-row={"属性 1"} data-col={"属性 2"} onClick={this.click}> | |
点击获取属性 | |
</div> | |
</div> | |
); | |
} | |
} |
31.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) | |
}) |
使用方法在源码 routes.js 有详细使用
32.React-Router
32.1 V3 和 V4 的区别
1.V3 或者说 V 早期版本是把 router 和 layout components 分开;
2.V4 是集中式 router, 通过 Route 嵌套,实现 Layout 和 page 嵌套,Layout 和 page 组件 是作为 router 的一部分
3. 在 V3 中的 routing 规则是 exclusive,意思就是最终只获取一个 route
4.V4 中的 routes 默认是 inclusive 的,这就意味着多个 <Route> 可以同时匹配和呈现. 如果只想匹配一个路由,可以使用 Switch,在 <Switch> 中只有一个 <Route> 会被渲染,同时可以再在每个路由添加 exact,做到精准匹配
Redirect,浏览器重定向,当多有都不匹配的时候,进行匹配
32.2 使用
import {HashRouter as Router, Switch} from "react-router-dom"; | |
class App extends React.Component{render(){ | |
const authPath = '/login' // 默认未登录的时候返回的页面,可以自行设置 | |
let authed = this.props.state.authed || localStorage.getItem('authed') // 如果登陆之后可以利用 redux 修改该值 | |
return ( | |
<Router> | |
<Switch> | |
{renderRoutes(routes, authed, authPath)} | |
</Switch> | |
</Router> | |
) | |
} | |
} |
V4 是通过 Route 嵌套,实现 Layout 和 page 嵌套,Switch 切换路由的作用
33. 样式引入方法
方式 1:import 导入
import './App.css';
方式 2: 内联方式
import React from 'react'; | |
const Header = () => { | |
const heading = '头部组件' | |
return(<div style={{backgroundColor:'orange'}}> | |
<h1>{heading}</h1> | |
</div> | |
) | |
} | |
或者 | |
import React from 'react'; | |
const footerStyle = { | |
width: '100%', | |
backgroundColor: 'green', | |
padding: '50px', | |
font: '30px', | |
color: 'white', | |
fontWeight: 'bold' | |
} | |
export const Footer = () => { | |
return(<div style={footerStyle}> | |
底部组件 | |
</div> | |
) | |
} |
34. 动态绑定 className
原理: 通过三元表达式控制 className 值
render(){ | |
const flag=true | |
return (<div className={flag?"active":"no-active"}> 这是技巧 34</div> | |
) | |
} |
总结
这就是从实际项目开发总结的 React 的 34 个技巧;
原创码字不易, 欢迎 star;
源码地址,请戳,欢迎 star