一、React 基本使用
1、JSX 的基本使用
变量、表达式
// 获取变量 插值
const pElem = <p>{this.state.name}</p>
return pElem
// 表达式
const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
return exprElem
class style
// class
const classElem = <p className="title">设置 css class</p>
return classElem
// style
const styleData = { fontSize: '30px', color: 'blue' }
const styleElem = <p style={styleData}>设置 style</p>
// 内联写法,注意 {{ 和 }}
// const styleElem = <p style={{ fontSize: '30px', color: 'blue' }}>设置 style</p>
return styleElem
子元素和组件
// 子元素
const imgElem = <div>
<p>我的头像</p>
<img src="xxxx.png"/>
<img src={this.state.imgUrl}/>
</div>
return imgElem
// 加载组件
const componentElem = <div>
<p>JSX 中加载一个组件</p>
<hr/>
<List/>
</div>
return componentElem
// 原生 html
const rawHtml = '<span>富文本内容<i>斜体</i><b>加粗</b></span>'
const rawHtmlData = {
__html: rawHtml // 注意,必须是这种格式
}
const rawHtmlElem = <div>
<p dangerouslySetInnerHTML={rawHtmlData}></p>
<p>{rawHtml}</p>
</div>
return rawHtmlElem
条件判断
if-else、 三元表达式、&&
import React from 'react'
import './style.css'
class ConditionDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'black'
}
}
render() {
const blackBtn = <button className="btn-black">black btn</button>
const whiteBtn = <button className="btn-white">white btn</button>
// // if else
// if (this.state.theme === 'black') {
// return blackBtn
// } else {
// return whiteBtn
// }
// // 三元运算符
// return <div>
// { this.state.theme === 'black' ? blackBtn : whiteBtn }
// </div>
// &&
return <div>
{ this.state.theme === 'black' && blackBtn }
</div>
}
}
export default ConditionDemo
渲染列表
import React from 'react'
class ListDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
return <ul>
{ /* vue v-for */
this.state.list.map(
(item, index) => {
// 这里的 key 和 Vue 的 key 类似,必填,不能是 index 或 random
return <li key={item.id}>
index {index}; id {item.id}; title {item.title}
</li>
}
)
}
</ul>
}
}
export default ListDemo
2、事件(重)
bind this
import React from 'react'
class EventDemo extends React.Component {
constructor(props) {
super(props)
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this)
}
render() {
// this - 使用 bind
return <p onClick={this.clickHandler1}>
{this.state.name}
</p>
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: 'lisi'
})
}
// 静态方法,this 指向当前实例
clickHandler2 = () => {
this.setState({
name: 'lisi'
})
}
export default EventDemo
event 传参 ( 合成事件 )
import React from 'react'
class EventDemo extends React.Component {
constructor(props) {
super(props)
}
render() {
// event
return <a href="https://imooc.com/" onClick={this.clickHandler3}>
click me
</a>
}
// 获取 event
clickHandler3 = (event) => {
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡
console.log('target', event.target) // 指向当前元素,即当前元素触发
console.log('current target', event.currentTarget) // 指向当前元素,假象!!!
// 注意,event 其实是 React 封装的。可以看 __proto__.constructor 是 SyntheticEvent 组合事件
console.log('event', event) // 不是原生的 Event ,原生的 MouseEvent
console.log('event.__proto__.constructor', event.__proto__.constructor)
// 原生 event 如下。其 __proto__.constructor 是 MouseEvent
console.log('nativeEvent', event.nativeEvent)
console.log('nativeEvent target', event.nativeEvent.target) // 指向当前元素,即当前元素触发
console.log('nativeEvent current target', event.nativeEvent.currentTarget) // 指向 document !!!
}
}
export default EventDemo
注意,
event其实是React封装的。可以看__proto__.constructor是SyntheticEvent组合事件
React中的event是SyntheticEvent(合成事件) ,模拟出来DOM事件所有能力
event.nativeEvent是原生事件对象所有的事件,都被挂载到
document上和
DOM事件不一样,和Vue事件也不一样
传递自定义参数
import React from 'react'
class EventDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'zhangsan',
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
// 传递参数 - 用 bind(this, a, b)
return <ul>{this.state.list.map((item, index) => {
return <li key={item.id} onClick={this.clickHandler4.bind(this, item.id, item.title)}>
index {index}; title {item.title}
</li>
})}</ul>
}
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title)
console.log('event', event) // 最后追加一个参数,即可接收 event
}
}
export default EventDemo
3、表单
受控组件
input值的来源是受react的this.state控制,属于受控组件
import React from 'react'
class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '双越',
info: '个人信息',
city: 'beijing',
flag: true,
gender: 'male'
}
}
render() {
// 受控组件(非受控组件,后面再讲)
return <div>
<p>{this.state.name}</p>
<label htmlFor="inputName">姓名:</label> {/* 用 htmlFor 代替 for */}
<input id="inputName" value={this.state.name} onChange={this.onInputChange}/>
</div>
// textarea - 使用 value
return <div>
<textarea value={this.state.info} onChange={this.onTextareaChange}/>
<p>{this.state.info}</p>
</div>
// select - 使用 value
return <div>
<select value={this.state.city} onChange={this.onSelectChange}>
<option value="beijing">北京</option>
<option value="shanghai">上海</option>
<option value="shenzhen">深圳</option>
</select>
<p>{this.state.city}</p>
</div>
// checkbox
return <div>
<input type="checkbox" checked={this.state.flag} onChange={this.onCheckboxChange}/>
<p>{this.state.flag.toString()}</p>
</div>
// radio
return <div>
male <input type="radio" name="gender" value="male" checked={this.state.gender === 'male'} onChange={this.onRadioChange}/>
female <input type="radio" name="gender" value="female" checked={this.state.gender === 'female'} onChange={this.onRadioChange}/>
<p>{this.state.gender}</p>
</div>
// 非受控组件 - 后面再讲
}
onInputChange = (e) => {
this.setState({
name: e.target.value
})
}
onTextareaChange = (e) => {
this.setState({
info: e.target.value
})
}
onSelectChange = (e) => {
this.setState({
city: e.target.value
})
}
onCheckboxChange = () => {
this.setState({
flag: !this.state.flag
})
}
onRadioChange = (e) => {
this.setState({
gender: e.target.value
})
}
}
export default FormDemo
4、父子组件传值
方法
父组件向子组件可以传递对象也可以传递一个方法,子组件通过const {obj, fn} = this.props获取传递的值或者方法。
类型检查 PropTypes
什么是
prop-types?prop代表父组件传递过来的值,types代表类型。简单来说就是用来校验父组件传递过来值的类型
propTypes用来规范props必须满足的类型,如果验证不通过将会有warn提示。
首先安装 prop-type,然后引入并使用:
import PropTypes from 'prop-types';
class Greeting extends React.Component {
render() {
return (
<h1>Hello, {this.props.name}</h1>
);
}
}
Greeting.propTypes = {
name: PropTypes.string
};
Demo
/**
* @description 演示 props 和事件
* @author 双越老师
*/
import React from 'react'
import PropTypes from 'prop-types'
class Input extends React.Component {
constructor(props) {
super(props)
this.state = {
title: ''
}
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onSubmit = () => {
// props 传递函数
const { submitTitle } = this.props
// 子组件直接调用 父组件 传递的函数
submitTitle(this.state.title) // 'abc'
this.setState({
title: ''
})
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
}
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
// props 传递属性
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
}
// props 类型检查
List.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props)
// 状态(数据)提升,父组件统一管理状态,子组件负责显示
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
return <div>
// 向子组件传递方法
<Input submitTitle={this.onSubmitTitle}/>
// 向子组件传递属性
<List list={this.state.list}/>
</div>
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
})
}
}
export default TodoListDemo
5、setState 为何使用不可变值(重要)
不可变值
不要直接修改 state ,原因是 shouldComponentUpdate, 后面讲。
值类型
// this.state.count++ // 错误
this.setState({
count: this.state.count + 1 // SCU
})
操作数组的常用形式
注意,不能直接对
this.state.list进行push pop splice等,这样违反不可变值
// 不可变值(函数式编程,纯函数) - 数组
const list5Copy = this.state.list5.slice()
list5Copy.splice(2, 0, 'a') // 中间插入/删除
this.setState({
list1: this.state.list1.concat(100), // 追加
list2: [...this.state.list2, 100], // 追加
list3: this.state.list3.slice(0, 3), // 截取
list4: this.state.list4.filter(item => item > 100), // 筛选
list5: list5Copy // 其他操作
})
对象
注意,不能直接对
this.state.obj进行属性设置,这样违反不可变值
// 不可变值 - 对象
this.setState({
obj1: Object.assign({}, this.state.obj1, {a: 100}),
obj2: {...this.state.obj2, a: 100}
})
可能是异步更新
1、
setState可能是异步更新(有可能是同步更新)
this.setState({
count: this.state.count + 1
}, () => {
// 联想 Vue $nextTick - DOM
console.log('count by callback', this.state.count) // 回调函数中可以拿到最新的 state
})
console.log('count', this.state.count) // 异步的,拿不到最新值
2、
setTimeout中setState是同步的
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('count in setTimeout', this.state.count)
}, 0)
3、 自己定义的
DOM事件,setState是同步的。
componentDidMount() {
// 自己定义的 DOM 事件,setState 是同步的
document.body.addEventListener('click', this.bodyClickHandler)
}
bodyClickHandler = () => {
this.setState({
count: this.state.count + 1
})
console.log('count in body event', this.state.count)
}
componentWillUnmount() {
// 及时销毁自定义 DOM 事件
document.body.removeEventListener('click', this.bodyClickHandler)
// clearTimeout
}
可能会被合并
setState中传入对象就会被合并,state异步更新的话,更新前会被合并
// 传入对象,会被合并(类似 Object.assign )。执行结果只执行一次 +1
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
serState传入函数,不会被合并。执行结果是 +3
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
this.setState((prevState, props) => {
return {
count: prevState.count + 1
}
})
6、React 生命周期
每个组件都包含 “生命周期方法”,你可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。
挂载阶段: 当组件实例被创建并插入 DOM 中时,其生命周期调用顺序如下:
constructor(): 在 React 组件挂载之前,会调用它的构造函数。
static getDerivedStateFromProps(): 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回
null则不更新任何内容。render(): 方法是 class 组件中唯一必须实现的方法。
render()函数应该为纯函数,这意味着在不修改组件 state 的情况下,每次调用时都返回相同的结果,并且它不会直接与浏览器交互。componentDidMount(): 会在组件挂载后(插入 DOM 树中)立即调用。依赖于 DOM 节点的初始化应该放在这里。如需通过网络请求获取数据,此处是实例化请求的好地方。
更新阶段: 当组件的 props 或 state 发生变化时会触发更新。组件更新的生命周期调用顺序如下:
static getDerivedStateFromProps(): 会在调用 render 方法之前调用,并且在初始挂载及后续更新时都会被调用。它应返回一个对象来更新 state,如果返回
null则不更新任何内容。shouldComponentUpdate(nextProps, nextState):
render():
getSnapshotBeforeUpdate()
componentDidUpdate():会在更新后会被立即调用。首次渲染不会执行此方法。
卸载阶段: 当组件从 DOM 中移除时会调用如下方法:
componentWillUnmount(): 会在组件卸载及销毁之前直接调用。在此方法中执行必要的清理操作,例如,清除 timer,取消网络请求或清除在
componentDidMount()中创建的订阅等。
二、React 高级特性
1、函数组件和class组件有什么区别?
2、什么是react非受控组件
定义
受控组件类似于
vue的双向数据绑定,数据和视图绑定在一起,非受控组件只是从dom中得到对应的数据(是单向的)
使用
1、react 创建 ref:
const nameRef = React.createRef()
2、非受控组件input使用defaultValue绑值,设置ref
// 非受控组件
return <div>
<input defaultValue={this.state.name} ref={this.nameRef}/>
<span>state.name: {this.state.name}</span>
<button onClick={this.alertName}>alert name</button>
</div>
3、使用this.nameRef.current.value获取值,获取的是DOM节点的值,没有响应式
alertName = () => {
console.log('state: ', this.state.name)
console.log('input name: ', this.inputRef.current.value)
}
Demo
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '双越',
flag: true,
}
this.nameInputRef = React.createRef() // 创建 ref
this.fileInputRef = React.createRef()
}
render() {
// // input defaultValue
// return <div>
// {/* 使用 defaultValue 而不是 value ,使用 ref */}
// <input defaultValue={this.state.name} ref={this.nameInputRef}/>
// {/* state 并不会随着改变 */}
// <span>state.name: {this.state.name}</span>
// <br/>
// <button onClick={this.alertName}>alert name</button>
// </div>
// // checkbox defaultChecked
// return <div>
// <input
// type="checkbox"
// defaultChecked={this.state.flag}
// />
// </div>
// file
return <div>
<input type="file" ref={this.fileInputRef}/>
<button onClick={this.alertFile}>alert file</button>
</div>
}
alertName = () => {
const elem = this.nameInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.value) // 不是 this.state.name
}
alertFile = () => {
const elem = this.fileInputRef.current // 通过 ref 获取 DOM 节点
alert(elem.files[0].name)
}
}
export default App
使用场景
受控组件 VS 非受控组件
3、什么场景需要 React Portals
Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container)
用法
通常来讲,当你从组件的 render 方法返回一个元素时,该元素将被挂载到 DOM 节点中离其最近的父节点:
import React from 'react'
import ReactDOM from 'react-dom'
import './style.css'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
}
}
render() {
// // 正常渲染
// return <div className="modal">
// {this.props.children} {/* vue slot */}
// </div>
// 使用 Portals 渲染到 body 上。
// fixed 元素要放在 body 上,有更好的浏览器兼容性。
return ReactDOM.createPortal(
<div className="modal">{this.props.children}</div>,
document.body // DOM 节点
)
}
}
export default App
style.css
.modal {
position: fixed;
width: 300px;
height: 100px;
top: 100px;
left: 50%;
margin-left: -150px;
background-color: #000;
/* opacity: .2; */
color: #fff;
text-align: center;
}
放在了body的第一层:
使用场景
4、是否用过 react context
作用
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
两个重点:
一个是使用场景: 一些公共信息如:修改主题、修改语言等;
一个是如何使用context
使用方法
1、创建Context
填入默认值(任何一个JS变量)
const ThemeContext = React.createContext('light')
2、管理和生产context
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
render() {
return <ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr/>
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>
}
changeTheme = () => {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
})
}
}
export default App
3、消费context
函数组件使用
函数组件实例,就没有 this,所以不能使用 this.context,可以使用 ThemeContext.Consumer
// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
console.log('themeLink', props)
// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
{ value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>
}
class组件使用
// 指定 contextType 读取当前的 theme context。
ThemedButton.contextType = ThemeContext
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
return <div>
<p>button's theme is {theme}</p>
</div>
}
}
demo
import React from 'react'
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light')
// 底层组件 - 函数是组件
function ThemeLink (props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
console.log('themeLink', props)
// 函数式组件可以使用 Consumer
return <ThemeContext.Consumer>
{ value => <p>link's theme is {value}</p> }
</ThemeContext.Consumer>
}
// 底层组件 - class 组件
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// static contextType = ThemeContext // 也可以用 ThemedButton.contextType = ThemeContext
render() {
const theme = this.context // React 会往上找到最近的 theme Provider,然后使用它的值。
return <div>
<p>button's theme is {theme}</p>
</div>
}
}
// 指定 contextType 读取当前的 theme context。
ThemedButton.contextType = ThemeContext
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(props) {
return (
<div>
<ThemedButton />
<ThemeLink />
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
theme: 'light'
}
}
render() {
return <ThemeContext.Provider value={this.state.theme}>
<Toolbar />
<hr/>
<button onClick={this.changeTheme}>change theme</button>
</ThemeContext.Provider>
}
changeTheme = () => {
this.setState({
theme: this.state.theme === 'light' ? 'dark' : 'light'
})
}
}
export default App
5、react 如何如何异步加载组件
Suspense 和 React.lazy()
Suspense使得组件可以“等待”某些操作结束后,再进行渲染。目前,Suspense仅支持的使用场景是:通过React.lazy动态加载组件。
React.lazy()允许你定义一个动态加载的组件。这有助于缩减 bundle 的体积,并延迟加载在初次渲染时未用到的组件。// 这个组件是动态加载的 const SomeComponent = React.lazy(() => import('./SomeComponent'));
demo
import React from 'react'
const ContextDemo = React.lazy(() => import('./ContextDemo'))
class App extends React.Component {
constructor(props) {
super(props)
}
render() {
return <div>
<p>引入一个动态组件</p>
<hr />
<React.Suspense fallback={<div>Loading...</div>}>
<ContextDemo/>
</React.Suspense>
</div>
// 1. 强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
// 2. 看 network 的 js 加载
}
}
export default App
6、性能优化
shouldComponentUpdate(简称 SCU)核心问题在哪里?
用法(SCU默认返回什么?)
接收两个参数,一个是nextProps, nextState,通过对比两个参数的值,如果相等就返回 false,不重复渲染;如果不等就返回true ,需要重新渲染。默认值返回true,可以进行性能优化。
SCU 背后的逻辑原理
react默认 父组件更新,子组件也会无条件更新,因此,性能优化对于react更重要。
SCU 每次都需要使用吗?不一定,性能优化,只是在需要的时候才使用
SCU 配合不可变值
错误写法解析:
如果数组使用push()等修改原数组的方法,那么修改前和修改后两者的引用地址是相同的.
再进行SCU比较的时候,前一个props 和 后一个 props 的值是完全一样的,所以不会重新渲染,导致页面不刷新的bug.
_.isEqual()是深拷贝方法,比较耗费性能,(一次性递归到底);会触发props的值的一次性递归到底。可以设置props的值层级不要太深,实现浅比较。
总结
Demo
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
render() {
return <div>
<span>{this.state.count}</span>
<button onClick={this.onIncrease}>increase</button>
</div>
}
onIncrease = () => {
this.setState({
count: this.state.count + 1
})
}
// 演示 shouldComponentUpdate 的基本使用
shouldComponentUpdate(nextProps, nextState) {
if (nextState.count !== this.state.count) {
return true // 可以渲染
}
return false // 不重复渲染
}
}
export default App
import React from 'react'
import PropTypes from 'prop-types'
import _ from 'lodash'
class Input extends React.Component {
constructor(props) {
super(props)
this.state = {
title: ''
}
}
render() {
return <div>
<input value={this.state.title} onChange={this.onTitleChange}/>
<button onClick={this.onSubmit}>提交</button>
</div>
}
onTitleChange = (e) => {
this.setState({
title: e.target.value
})
}
onSubmit = () => {
const { submitTitle } = this.props
submitTitle(this.state.title)
this.setState({
title: ''
})
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired
}
class List extends React.Component {
constructor(props) {
super(props)
}
render() {
const { list } = this.props
return <ul>{list.map((item, index) => {
return <li key={item.id}>
<span>{item.title}</span>
</li>
})}</ul>
}
// 增加 shouldComponentUpdate
shouldComponentUpdate(nextProps, nextState) {
// _.isEqual 做对象或者数组的深度比较(一次性递归到底)
if (_.isEqual(nextProps.list, this.props.list)) {
// 相等,则不重复渲染
return false
}
return true // 不相等,则渲染
}
}
// props 类型检查
List.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired
}
class TodoListDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
list: [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
]
}
}
render() {
return <div>
<Input submitTitle={this.onSubmitTitle}/>
<List list={this.state.list}/>
</div>
}
onSubmitTitle = (title) => {
// 正确的用法
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title
})
})
// // 为了演示 SCU ,故意写的错误用法,违反了 state 不可变值
// this.state.list.push({
// id: `id-${Date.now()}`,
// title
// })
// this.setState({
// list: this.state.list
// })
}
}
export default TodoListDemo
PureComponent( 纯组件 )
大部分情况下,你可以使用 React.PureComponent 来代替手写 shouldComponentUpdate。但它只进行浅比较,所以当 props 或者 state 某种程度是可变的话,浅比较会有遗漏,那你就不能使用它了。
memo
注意:
与 class 组件中 shouldComponentUpdate() 方法不同的是,如果 props 相等,areEqual 会返回 true;如果 props 不相等,则返回 false。这与 shouldComponentUpdate 方法的返回值相反。
不可变值 immutable.js
7、高阶组件HOC
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
具体而言,高阶组件是参数为组件,返回值为新组件的函数。
组件公共逻辑的抽离
demo案例:
import React from 'react'
// 高阶组件
const withMouse = (Component) => {
class withMouseComponent extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 1. 透传所有 props 2. 增加 mouse 属性 */}
<Component {...this.props} mouse={this.state}/>
</div>
)
}
}
return withMouseComponent
}
const App = (props) => {
const a = props.a
const { x, y } = props.mouse // 接收 mouse 属性
return (
<div style={{ height: '500px' }}>
<h1>The mouse position is ({x}, {y})</h1>
<p>{a}</p>
</div>
)
}
export default withMouse(App) // 返回高阶函数
connect 源码:
8、render props
术语 “render prop” 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
demo
import React from 'react'
import PropTypes from 'prop-types'
class Mouse extends React.Component {
constructor(props) {
super(props)
this.state = { x: 0, y: 0 }
}
handleMouseMove = (event) => {
this.setState({
x: event.clientX,
y: event.clientY
})
}
render() {
return (
<div style={{ height: '500px' }} onMouseMove={this.handleMouseMove}>
{/* 将当前 state 作为 props ,传递给 render (render 是一个函数组件) */}
{this.props.render(this.state)}
</div>
)
}
}
Mouse.propTypes = {
render: PropTypes.func.isRequired // 必须接收一个 render 属性,而且是函数
}
const App = (props) => (
<div style={{ height: '500px' }}>
<p>{props.a}</p>
<Mouse render={
/* render 是一个函数组件 */
({ x, y }) => <h1>The mouse position is ({x}, {y})</h1>
}/>
</div>
)
/**
* 即,定义了 Mouse 组件,只有获取 x y 的能力。
* 至于 Mouse 组件如何渲染,App 说了算,通过 render prop 的方式告诉 Mouse 。
*/
export default App
HOC VS Render Props
三、周边工具
1、 Redux使用
基本概念
单项数据流
react-redux
异步 action
中间件
2、react-router
四、React 原理
1、函数式编程
2、vdom 和 diff算法
3、JSX 的本质
// https://www.babeljs.cn/
// // JSX 基本用法
// const imgElem = <div id="div1">
// <p>some text</p>
// <img src={imgUrl}/>
// </div>
// // JSX style
// const styleData = { fontSize: '30px', color: 'blue' }
// const styleElem = <p style={styleData}>设置 style</p>
// // JSX 加载组件
// const app = <div>
// <Input submitTitle={onSubmitTitle}/>
// <List list={list}/>
// </div>
// // JSX 事件
// const eventList = <p onClick={this.clickHandler}>
// some text
// </p>
// // JSX list
// const listElem = <ul>{this.state.list.map((item, index) => {
// return <li key={item.id}>index {index}; title {item.title}</li>
// })}</ul>
// // 总结
// React.createElement('div', null, [child1, child2, child3])
// React.createElement('div', {...}, child1, child2, child3)
// React.createElement(List, null, child1, child2, '文本节点')
// // h 函数
// // 返回 vnode
// // patch
4、合成事件
为何要用合成事件机制 ?
5、setState、 batchUpdate 机制
setState 主流程
setTimeout 回调函数:
自定义 DOM 事件: