脚手架
基本语法
- jsx 语法
- 基本语法
import React from 'react'
import './style.css'
import List from '../List'
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'xmllein',
imgUrl: 'https://img1.mukewang.com/5a9fc8070001a82402060220-140-140.jpg',
flag: true,
}
}
render() {
// // 获取变量 插值
// const pElem = <p>{this.state.name}</p>
// return pElem
// // 表达式
// const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
// return exprElem
// // 子元素
// const imgElem = <div>
// <p>我的头像</p>
// <img src="xxxx.png"/>
// <img src={this.state.imgUrl}/>
// </div>
// return imgElem
// // 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
// 原生 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
// // 加载组件
// const componentElem = <div>
// <p>JSX 中加载一个组件</p>
// <hr/>
// <List/>
// </div>
// return componentElem
}
}
export default JSXBaseDemo
import React from 'react'
class List extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'React',
list: ['a', 'b', 'c'],
}
}
render() {
return (
<div>
<p onClick={this.changeName.bind(this)}>{this.state.name}</p>
<ul>
{this.state.list.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
<button onClick={this.addItem.bind(this)}>添加一项</button>
</div>
)
}
changeName() {
this.setState({
name: 'xmllein',
})
}
addItem() {
this.setState({
list: this.state.list.concat(`${Date.now()}`), // 使用不可变值
})
}
}
export default List
- 条件
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
- 事件
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',
},
],
}
// 修改方法的 this 指向
this.clickHandler1 = this.clickHandler1.bind(this)
}
render() {
// // this - 使用 bind
// return <p onClick={this.clickHandler1}>
// {this.state.name}
// </p>
// // this - 使用静态方法
// return <p onClick={this.clickHandler2}>
// clickHandler2 {this.state.name}
// </p>
// // event
// return <a href="https://imooc.com/" onClick={this.clickHandler3}>
// click me
// </a>
// 传递参数 - 用 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>
)
}
clickHandler1() {
// console.log('this....', this) // this 默认是 undefined
this.setState({
name: 'lisi',
})
}
// 静态方法,this 指向当前实例
clickHandler2 = () => {
this.setState({
name: 'lisi',
})
}
// 获取 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 !!!
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象 react 17 绑到root上
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
}
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title)
console.log('event', event) // 最后追加一个参数,即可接收 event
}
}
export default EventDemo
- 表单
import React from 'react'
class FormDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'xmllein',
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
- 组件和 props(类型检查)
- prop-types: 类型检查。
/**
* @description 演示 props 和事件
* @author xmllein老师
*/
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 = () => {
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() {
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 Footer extends React.Component {
constructor(props) {
super(props)
}
render() {
return (
<p>
{this.props.text}
{this.props.length}
</p>
)
}
componentDidUpdate() {
console.log('footer did update')
}
shouldComponentUpdate(nextProps, nextState) {
if (
nextProps.text !== this.props.text ||
nextProps.length !== this.props.length
) {
return true // 可以渲染
}
return false // 不重复渲染
}
// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化
}
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',
},
],
footerInfo: '底部文字',
}
}
render() {
return (
<div>
<Input submitTitle={this.onSubmitTitle} />
<List list={this.state.list} />
<Footer text={this.state.footerInfo} length={this.state.list.length} />
</div>
)
}
onSubmitTitle = (title) => {
this.setState({
list: this.state.list.concat({
id: `id-${Date.now()}`,
title,
}),
})
}
}
export default TodoListDemo
- state 和 setState
import React from 'react'
// 函数组件(后面会讲),默认没有 state
class StateDemo extends React.Component {
constructor(props) {
super(props)
// 第一,state 要在构造函数中定义
this.state = {
count: 0,
}
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
)
}
increase = () => {
// // 第二,不要直接修改 state ,使用不可变值 ----------------------------
// // this.state.count++ // 错误
// this.setState({
// count: this.state.count + 1 // SCU
// })
// 操作数组、对象的的常用形式
// 第三,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) // 异步的,拿不到最新值
// // setTimeout 中 setState 是同步的
// setTimeout(() => {
// this.setState({
// count: this.state.count + 1
// })
// console.log('count in setTimeout', this.state.count)
// }, 0)
// 自己定义的 DOM 事件,setState 是同步的。再 componentDidMount 中
// 第四,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
// })
// 传入函数,不会被合并。执行结果是 +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,
}
})
}
// bodyClickHandler = () => {
// this.setState({
// count: this.state.count + 1
// })
// console.log('count in body event', this.state.count)
// }
// componentDidMount() {
// // 自己定义的 DOM 事件,setState 是同步的
// document.body.addEventListener('click', this.bodyClickHandler)
// }
// componentWillUnmount() {
// // 及时销毁自定义 DOM 事件
// document.body.removeEventListener('click', this.bodyClickHandler)
// // clearTimeout
// }
}
export default StateDemo
// -------------------------- 我是分割线 -----------------------------
// // 不可变值(函数式编程,纯函数) - 数组
// 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.list 进行 push pop splice 等,这样违反不可变值
// // 不可变值 - 对象
// this.setState({
// obj1: Object.assign({}, this.state.obj1, {a: 100}),
// obj2: {...this.state.obj2, a: 100}
// })
// // 注意,不能直接对 this.state.obj 进行属性设置,这样违反不可变值
组件生命周期
高级使用
函数组件
- 函数组件和类组件的区别
- 纯函数
- 函数组件没有实例,没有生命周期,没有 state
- 函数组件没有 this,没有 refs
- 函数组件和类组件的区别
// 函数组件
function List(props) {
const { list } = this.props
return (
<ul>
{list.map((item) => (
<li key={item.id}>{item.name}</li>
))}
</ul>
)
}
受控与非受控组件
- 受控组件:表单元素的值受 state 控制
- 非受控组件:表单元素的值不受 state 控制
import React from 'react'
class App extends React.Component {
constructor(props) {
super(props)
this.state = {
name: 'xmllein',
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
refs
Portals
- 什么是 Portals?:Portals 可以让你把子节点渲染到父组件 DOM 层次以外的 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
context
- 什么是 context?:context 可以让你不通过 props 层层传递数据,就能在组件树中传递数据
import React from 'react'
// 创建 Context 填入默认值(任何一个 js 变量)
const ThemeContext = React.createContext('light')
// 底层组件 - 函数是组件
function ThemeLink(props) {
// const theme = this.context // 会报错。函数式组件没有实例,即没有 this
// 函数式组件可以使用 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>
)
}
}
ThemedButton.contextType = ThemeContext // 指定 contextType 读取当前的 theme context。
// 中间的组件再也不必指明往下传递 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
异步组件
- React 异步组件的实现原理是什么?:React.lazy 和 Suspense
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
性能优化
- React.memo: React.memo 是一个高阶组件,它与 React.PureComponent 非常相似,但它适用于函数组件,但不适用于 class 组件。
scu
- shouldComponentUpdate: shouldComponentUpdate() 会在渲染执行之前被调用。返回值默认为 true。首次渲染或使用 forceUpdate() 时不会调用该方法。
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 ,故意写的错误用法
// this.state.list.push({
// id: `id-${Date.now()}`,
// title
// })
// this.setState({
// list: this.state.list
// })
}
}
export default TodoListDemo
纯组件
- PureComponent: PureComponent 与 Component 非常相似。它们的区别在于 PureComponent 通过 prop 和 state 的浅比较来实现 shouldComponentUpdate()。
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 = () => {
const { submitTitle } = this.props
submitTitle(this.state.title)
this.setState({
title: '',
})
}
}
// props 类型检查
Input.propTypes = {
submitTitle: PropTypes.func.isRequired,
}
class List extends React.PureComponent {
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() {
/*浅比较*/
}
}
// 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 ,故意写的错误用法
// this.state.list.push({
// id: `id-${Date.now()}`,
// title
// })
// this.setState({
// list: this.state.list
// })
}
}
export default TodoListDemo
不可变值(immutable)
高阶组件
- 高阶组件是一个函数,且该函数接受一个组件作为参数,并返回一个新的组件。
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) // 返回高阶函数
render props
- render props 是指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术。
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
Hooks
useState
useState
是一个函数,它的作用是用来声明状态变量。
import React, { useState } from 'react'
// 子组件
function Child({ userInfo }) {
// render: 初始化 state
// re-render: 只恢复初始化的 state 值,不会再重新设置新的值
// 只能用 setName 修改
const [name, setName] = useState(userInfo.name)
return (
<div>
<p>Child, props name: {userInfo.name}</p>
<p>Child, state name: {name}</p>
</div>
)
}
function App() {
const [name, setName] = useState('xmllein')
const userInfo = { name }
return (
<div>
<div>
Parent
<button onClick={() => setName('测试React')}>setName</button>
</div>
<Child userInfo={userInfo} />
</div>
)
}
export default App
useEffect
useEffect
是一个函数,它的作用是用来声明副作用操作。其作用:模拟类组件的生命周期函数。
import React, { useState, useEffect } from 'react'
function LifeCycles() {
const [count, setCount] = useState(0)
const [name, setName] = useState('xmllein')
// // 模拟 class 组件的 DidMount 和 DidUpdate
// useEffect(() => {
// console.log('在此发送一个 ajax 请求')
// })
// // 模拟 class 组件的 DidMount
// useEffect(() => {
// console.log('加载完了')
// }, []) // 第二个参数是 [] (不依赖于任何 state)
// // 模拟 class 组件的 DidUpdate
// useEffect(() => {
// console.log('更新了')
// }, [count, name]) // 第二个参数就是依赖的 state
// 模拟 class 组件的 DidMount
useEffect(() => {
let timerId = window.setInterval(() => {
console.log(Date.now())
}, 1000)
// 返回一个函数
// 模拟 WillUnMount
return () => {
window.clearInterval(timerId)
}
}, [])
function clickHandler() {
setCount(count + 1)
setName(name + '2020')
}
return (
<div>
<p>
你点击了 {count} 次 {name}
</p>
<button onClick={clickHandler}>点击</button>
</div>
)
}
export default LifeCycles
useContext
useContext
是一个函数,它的作用是用来在函数组件中使用 context。其作用: 接收一个 context 对象(React.createContext
的返回值)并返回该 context 的当前值。当前的 context 值由上层组件中距离当前组件最近的<MyContext.Provider>
的value
prop 决定。
import React, { useContext } from 'react'
// 主题颜色
const themes = {
light: {
foreground: '#000',
background: '#eee',
},
dark: {
foreground: '#fff',
background: '#222',
},
}
// 创建 Context
const ThemeContext = React.createContext(themes.light) // 初始值
function ThemeButton() {
const theme = useContext(ThemeContext)
return (
<button style={{ background: theme.background, color: theme.foreground }}>
hello world
</button>
)
}
function Toolbar() {
return (
<div>
<ThemeButton></ThemeButton>
</div>
)
}
function App() {
return (
<ThemeContext.Provider value={themes.dark}>
<Toolbar></Toolbar>
</ThemeContext.Provider>
)
}
export default App
useReducer
useReducer
是一个函数,它的作用是用来声明 reducer。其作用: 模拟类组件的setState
。
import React, { useReducer } from 'react'
const initialState = { count: 0 }
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return { count: state.count + 1 }
case 'decrement':
return { count: state.count - 1 }
default:
return state
}
}
function App() {
// 很像 const [count, setCount] = useState(0)
const [state, dispatch] = useReducer(reducer, initialState)
return (
<div>
count: {state.count}
<button onClick={() => dispatch({ type: 'increment' })}>increment</button>
<button onClick={() => dispatch({ type: 'decrement' })}>decrement</button>
</div>
)
}
export default App
useCallback
useCallback
是一个函数,它的作用是用来缓存函数。其作用:缓存函数,避免不必要的渲染。
import React, { useState, memo, useMemo, useCallback } from 'react'
// 子组件,memo 相当于 PureComponent
const Child = memo(({ userInfo, onChange }) => {
console.log('Child render...', userInfo)
return (
<div>
<p>
This is Child {userInfo.name} {userInfo.age}
</p>
<input onChange={onChange}></input>
</div>
)
})
// 父组件
function App() {
console.log('Parent render...')
const [count, setCount] = useState(0)
const [name, setName] = useState('xmllein')
// 用 useMemo 缓存数据
const userInfo = useMemo(() => {
return { name, age: 21 }
}, [name])
// function onChange(e) {
// console.log(e.target.value)
// }
// 用 useCallback 缓存函数
const onChange = useCallback((e) => {
console.log(e.target.value)
}, [])
return (
<div>
<p>
count is {count}
<button onClick={() => setCount(count + 1)}>click</button>
</p>
<Child userInfo={userInfo} onChange={onChange}></Child>
</div>
)
}
export default App
useMemo
useMemo
是一个函数,它的作用是用来缓存数据。其作用:缓存数据,避免不必要的渲染。
import React, { useState, memo, useMemo } from 'react'
// 子组件
// function Child({ userInfo }) {
// console.log('Child render...', userInfo)
// return <div>
// <p>This is Child {userInfo.name} {userInfo.age}</p>
// </div>
// }
// 类似 class PureComponent ,对 props 进行浅层比较
const Child = memo(({ userInfo }) => {
console.log('Child render...', userInfo)
return (
<div>
<p>
This is Child {userInfo.name} {userInfo.age}
</p>
</div>
)
})
// 父组件
function App() {
console.log('Parent render...')
const [count, setCount] = useState(0)
const [name, setName] = useState('xmllein')
// const userInfo = { name, age: 20 }
// 用 useMemo 缓存数据,有依赖
const userInfo = useMemo(() => {
return { name, age: 21 }
}, [name])
return (
<div>
<p>
count is {count}
<button onClick={() => setCount(count + 1)}>click</button>
</p>
<Child userInfo={userInfo}></Child>
</div>
)
}
export default App
useRef
useRef
是一个函数,获取 DOM 节点或者保存变量。其作用:获取 DOM 节点或者保存变量。
import React, { useRef, useEffect } from 'react'
function UseRef() {
const btnRef = useRef(null) // 初始值
// const numRef = useRef(0)
// numRef.current
useEffect(() => {
console.log(btnRef.current) // DOM 节点
}, [])
return (
<div>
<button ref={btnRef}>click</button>
</div>
)
}
export default UseRef
useImperativeHandle
useImperativeHandle
是一个函数,它的作用是用来自定义暴露给父组件的实例值。其作用:自定义暴露给父组件的实例值。
import React, { useRef, useImperativeHandle, forwardRef } from 'react'
// 子组件
const Child = forwardRef((props, ref) => {
const inputRef = useRef(null)
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus()
},
}))
return (
<div>
<input ref={inputRef}></input>
</div>
)
})
// 父组件
function UseImperativeHandle() {
const childRef = useRef(null)
return (
<div>
<button onClick={() => childRef.current.focus()}>focus</button>
<Child ref={childRef}></Child>
</div>
)
}
export default UseImperativeHandle
useLayoutEffect
useLayoutEffect
是一个函数,它的作用是在 DOM 更新完成后执行。其作用:在 DOM 更新完成后执行。
import React, { useState, useEffect, useLayoutEffect } from 'react'
function UseLayoutEffect() {
const [count, setCount] = useState(0)
useEffect(() => {
if (count === 0) {
const now = Date.now()
while (Date.now() - now < 3000) {}
}
}, [count])
useLayoutEffect(() => {
if (count === 0) {
const now = Date.now()
while (Date.now() - now < 3000) {}
}
}, [count])
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>click</button>
</div>
)
}
export default UseLayoutEffect
- 自定义 Hooks
import { useState, useEffect } from 'react'
function useMousePosition() {
const [x, setX] = useState(0)
const [y, setY] = useState(0)
useEffect(() => {
function mouseMoveHandler(event) {
setX(event.clientX)
setY(event.clientY)
}
// 绑定事件
document.body.addEventListener('mousemove', mouseMoveHandler)
// 解绑事件
return () =>
document.body.removeEventListener('mousemove', mouseMoveHandler)
}, [])
return [x, y]
}
export default useMousePosition
import { useState, useEffect } from 'react'
import axios from 'axios'
// 封装 axios 发送网络请求的自定义 Hook
function useAxios(url) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
// 利用 axios 发送网络请求
setLoading(true)
axios
.get(url) // 发送一个 get 请求
.then((res) => setData(res))
.catch((err) => setError(err))
.finally(() => setLoading(false))
}, [url])
return [loading, data, error]
}
export default useAxios
// 第三方 Hook
// https://nikgraf.github.io/react-hooks/
// https://github.com/umijs/hooks
为何使用 Hooks,要依赖调用顺序?
- 为了保证每次渲染都有相同的顺序,这样才能保证每次渲染都是相同的结果。
class 组件中有什么问题?
- 代码冗余
- 逻辑分散
- this 指向问题
- 复用问题
逻辑复用
HOC
:高阶组件render props
:渲染属性Hooks
:钩子函数
周边工具
redux-toolkit
是什么?:redux-toolkit
是一个redux
工具包,它可以让你更加方便的使用redux
。- configureStore:
configureStore({ reducer, middleware, devTools, preloadedState, enhancers })
- createSlice:
createSlice({ name, initialState, reducers, extraReducers, sliceCaseReducers })
- createAsyncThunk:
createAsyncThunk(typePrefix, payloadCreator, options)
- createEntityAdapter:
createEntityAdapter({ selectId, sortComparer, ...rest })
- createReducer:
createReducer(initialState, builderCallback)
- createNextState:
createNextState(baseState, draft => {})
- createSerializableStateInvariantMiddleware:
createSerializableStateInvariantMiddleware(options)
- createImmutableStateInvariantMiddleware:
createImmutableStateInvariantMiddleware(options)
- createMiddleware:
createMiddleware(options)
- createAction:
createAction(type, prepareAction)
- store:
createStore(reducer, [preloadedState], [enhancer])
- action:
dispatch(action)
- reducer:
(state, action) => newState
- middleware:
({ getState, dispatch }) => next => action => {}
- dispatch:
dispatch(action)
- subscribe:
subscribe(listener)
:listener
会在dispatch
时被调用
- store:
provider:
<Provider store>
connect:
connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])
useSelector:
useSelector(selector, equalityFn)
useDispatch:
useDispatch()
useStore:
useStore()
thunk
是什么?:({ getState, dispatch }) => next => action => {}
:action
可以是一个函数,函数接收dispatch
和getState
作为参数。
dva
是什么?:dva
是基于redux
和redux-saga
的数据流方案。mobx
是什么?:mobx
是一个状态管理库,它可以让你的应用状态变得可预测。- observable:
observable(value, options?, name?)
- computed:
computed(() => value, options?, name?)
- autorun:
autorun(() => {})
- reaction:
reaction(() => value, (value, reaction) => {})
- action:
action(() => {})
- runInAction:
runInAction(() => {})
- when:
when(() => value, () => {})
- whyRun:
whyRun(() => {})
- spy:
spy(() => {})
- toJS:
toJS(value, options?)
- toJSON:
toJSON(value, options?)
- isObservable:
isObservable(value)
- isObservableObject:
isObservableObject(value)
immer:
immer
是一个不可变值库,它可以让你以mutate
的方式来修改不可变值。react-router:
react-router
是一个路由库,它可以让你的应用拥有路由功能。redux-persist:
redux-persist
是一个持久化存储库,它可以让你的应用状态持久化存储。ahooks:
ahooks
是一个 React Hooks 库,它可以让你的应用更加简单。
Typescript 整合
- React 与 TS 配合之基础 props 限制
- React 与 TS 配合之 children 与 event 限制
- React 与 TS 配合之 style 与 component 限制
- React 与 TS 配合之 use 函数限制
import React from 'react'
interface HeaderProps {
username: string
}
interface WelcomeProps {
name?: string
count?: number
list: string[]
info: { username: string; age: number }
status?: 'loading' | 'success' | 'error'
// 子元素
children?: React.ReactNode
// 事件
handleClick?: (e: React.MouseEvent<HTMLButtonElement>) => void
// 样式
style?: React.CSSProperties
// 组件
component: React.ComponentType<HeaderProps>
}
// const Welcome = (props: WelcomeProps) => {
// return (
// <h1>
// Hello, {props.name}, {props.count}
// </h1>
// )
// }
function Header(props: HeaderProps) {
return <div>hello Header</div>
}
// 等同于
const Welcome: React.FC<WelcomeProps> = (props) => {
// 解构赋值 默认值
const { name = 'world', count } = props
// 类型断言
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
console.log(e.target.value)
}
// useState
const [msg, setMsg] = React.useState<number | string>('hello')
// useRef
const myRef = React.useRef<HTMLInputElement>(null)
// useEffect
React.useEffect(() => {
console.log(myRef.current?.value) // 可选链(类型保护)
console.log(myRef.current!.value!.length) // 非空断言, 一定有值(慎用)
}, [])
const handleClick = () => {
setMsg('world')
console.log(myRef.current?.value)
}
return (
<>
<h1>
Hello, {name}, {count}
</h1>
<ul>
{props.list.map((item, index) => {
return <li key={index}>{item}</li>
})}
</ul>
<p>{props.info.age}</p>
<div>{props.children}</div>
<button onClick={props.handleClick}>点击事件</button>
<input type='text' value={count} onChange={handleChange} ref={myRef} />
<p>msg: {msg}</p>
<button onClick={handleClick}>修改msg</button>
<div>
<props.component username='hello'></props.component>
</div>
</>
)
}
// 默认值
// Welcome.defaultProps = {
// name: 'hello',
// }
export default function Demo01() {
return (
<div>
<h2>Demo01</h2>
<Welcome
count={123}
list={['a', 'b', 'c']}
info={{ username: 'hello', age: 1200 }}
status='loading'
style={{ color: 'red' }}
component={Header}
handleClick={(ev) => {
console.log(ev)
}}
>
hello world
</Welcome>
</div>
)
}
- React 与 TS 配合之类组件类型限制
import React, { Component } from 'react'
interface WelcomeProps {
msg: string
count: number
}
interface WelcomeState {
username: string
}
class Welcome extends Component<WelcomeProps, WelcomeState> {
state = {
username: 'xiaoming',
}
render() {
return <div>hello Welcome {this.state.username}</div>
}
}
export default function App() {
return (
<div>
<h2>05_react-ts</h2>
<Welcome msg='hello' count={123} />
</div>
)
}
- ReactRouter 路由如何使用 TS 进行开发
import React from 'react'
import { createBrowserRouter } from 'react-router-dom'
import type { RouteObject } from 'react-router-dom'
import App from '../App'
import Home from '../views/Home'
import About from '../views/About'
import Login from '../views/Login'
// 声明路由表的类型
declare module 'react-router' {
interface NonIndexRouteObject {
meta?: {
title: string
}
}
interface IndexRouteObject {
meta?: {
title: string
}
}
}
// 路由表
export const routes: RouteObject[] = [
{
path: '/',
element: React.createElement(App),
meta: { title: '首页' },
children: [
{
path: 'home',
element: React.createElement(Home),
meta: { title: 'home' },
},
{
path: 'about',
element: React.createElement(About),
meta: { title: 'About' },
},
{
path: 'login',
element: React.createElement(Login),
meta: { title: 'Login' },
},
],
},
]
const router = createBrowserRouter(routes)
export default router
import React from 'react'
import ReactDOM from 'react-dom/client'
import './index.css'
import reportWebVitals from './reportWebVitals'
import { RouterProvider } from 'react-router-dom'
import router from './router'
import { Provider } from 'react-redux'
import store from './store'
const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement)
root.render(
<React.StrictMode>
<Provider store={store}>
<RouterProvider router={router}></RouterProvider>
</Provider>
</React.StrictMode>
)
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals()
- ReduxToolkit 状态管理如何使用 TS 进行开发
import { configureStore } from '@reduxjs/toolkit'
import { useDispatch } from 'react-redux'
import counterReducer from './modules/counter'
import messageReducer from './modules/message'
const store = configureStore({
reducer: {
counter: counterReducer,
message: messageReducer,
},
})
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch
export const useAppDispatch = () => useDispatch<AppDispatch>()
export default store
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
type CounterState = { count: number; doubleCount: number }
// type Counter = { count: number }
const initialState: CounterState = { count: 0, doubleCount: 0 }
export const counterTestActions = createAsyncThunk(
'counter/testAction',
async () => {
const ret = await new Promise((resolve) => {
setTimeout(() => {
resolve({ count: 10 })
}, 2000)
})
return ret
}
)
const counterSlice = createSlice({
name: 'counter',
initialState: {
...initialState,
doubleCount: initialState.count * 2,
} as CounterState,
reducers: {
increment(state, action: PayloadAction<number>) {
state.count += action.payload
state.doubleCount = state.count * 2
},
decrement(state) {
state.count -= 1
},
},
extraReducers: (builders) => {
// 这里的 action 是 createAsyncThunk 返回的 action
builders
.addCase(counterTestActions.fulfilled, (state, action) => {
// state.count = (action.payload as Counter).count
const { count } = action.payload as { [index: string]: unknown }
state.count = count as number
state.doubleCount = state.count * 2
})
.addCase(counterTestActions.pending, (state) => {
console.log('pending')
})
.addCase(counterTestActions.rejected, (state) => {
console.log('rejected')
})
},
})
export default counterSlice.reducer
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'
import type { PayloadAction } from '@reduxjs/toolkit'
// 异步
export const messageTestAction = createAsyncThunk(
'message/testAction',
async () => {
const ret = await new Promise((resolve) => {
setTimeout(() => {
resolve({ msg: 'hello async' })
}, 2000)
})
return ret
}
)
const messageSlice = createSlice({
name: 'message',
initialState: { msg: 'hello' },
reducers: {
change(state, action: PayloadAction<string>) {
state.msg = action.payload
},
reset(state) {
state.msg = 'hello'
},
},
})
export default messageSlice.reducer
import React from 'react'
import { useSelector } from 'react-redux'
import type { RootState } from '../store'
import { useAppDispatch } from '../store'
import { counterTestActions } from '../store/modules/counter'
export default function Login() {
const count = useSelector((state: RootState) => state.counter.count)
const doubleCount = useSelector(
(state: RootState) => state.counter.doubleCount
)
const dispatch = useAppDispatch()
const handleClick = () => {
// dispatch({ type: 'counter/increment', payload: 1 })
dispatch(counterTestActions())
}
return (
<div>
<h1>Login</h1>
<p>count: {count}</p>
<p>doubleCount: {doubleCount}</p>
<button onClick={handleClick}>修改count</button>
</div>
)
}
面试题目
- 打印出 state 值
import React from 'react'
class ListDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0,
}
}
render() {
return <p>{this.state.count}</p>
}
componentDidMount() {
// count 初始值为 0
this.setState({ count: this.state.count + 1 })
console.log('1', this.state.count) // 0
this.setState({ count: this.state.count + 1 })
console.log('2', this.state.count) // 0
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('3', this.state.count) // 2
})
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('4', this.state.count) // 3
})
}
}
export default ListDemo
React fiber 是什么?
fiber
是一个数据结构,它可以让React
更加高效的执行渲染任务。React fiber 为什么要使用链表?:
fiber
使用链表的原因是因为链表的插入和删除操作比较快。- 将 reconciliation 阶段进行任务拆分(commit 无法拆分)
- dom 需要渲染的时候,可以暂停任务,让出主线程,让浏览器渲染
- window.requestIdleCallback
vdom 与 diff 算法
vdom
是什么?:vdom
是一个虚拟dom
,它可以让我们在js
中描述dom
结构。h 函数:
h(type, props, ...children)
vnode 数据结构:
{ type, props, children }
patch 函数:
patch(container, vnode)
diff 算法:
diff
算法是一种算法,它可以让我们找出两个vnode
之间的差异。diff 算法策略:
diff
算法的策略是O(n)
,它会遍历新旧两个vnode
,然后找出差异。- 同级比较:
diff
算法会先比较同级节点,如果同级节点不同,那么就会直接替换。 - tag 不相同:
diff
算法会直接替换。 - tag 和 key 两者相同, 但是 props 不同:
diff
算法会更新 props。 - tag 和 key 两者相同, 但是 children 不同:
diff
算法会更新 children。 - tag 和 key 两者相同, 但是 props 和 children 都不同:
diff
算法会更新 props 和 children。 - tag 和 key 两者相同, 但是 props 和 children 都相同:
diff
算法不会更新。
- 同级比较:
React 事件和 dom 事件区别:
- React 事件是合成事件,它是通过事件委托的方式来实现的。
- dom 事件是原生事件,它是通过在 dom 上直接绑定事件来实现的。
- React 事件和 dom 事件的区别是: React 事件是通过事件委托的方式来实现的,而 dom 事件是通过在 dom 上直接绑定事件来实现的。
React 性能优化:
- 使用
shouldComponentUpdate
或者React.memo
来避免不必要的渲染。 - 使用
useCallback
来避免不必要的渲染。 - 使用
useMemo
来避免不必要的渲染。 - 使用
React.lazy
和Suspense
来实现代码分割。 - 渲染列表的时候,使用
key
来提高渲染性能。 - 自定义事件,dom 事件,要及时销毁
- 减少 bind this 操作,使用箭头函数
- 合理使用
immutable.js
,immer.js
等不可变值库
- 使用
React 和 Vue 区别?
相同:
- 都是组件化开发
- 都是声明式开发
- 都是数据驱动视图
- 都是使用虚拟 dom
- 都是函数式编程
不同:
- React 是通过 JSX 来实现模板的,而 Vue 是通过 template 来实现模板的。
- React 是通过 props 来实现组件之间的通信的,而 Vue 是通过 props 和 emit 来实现组件之间的通信的。
- React 是通过 context 来实现跨层级通信的,而 Vue 是通过 provide 和 inject 来实现跨层级通信的。
- React 是通过高阶组件来实现代码复用的,而 Vue 是通过 mixins 来实现代码复用的。
- React 是通过函数组件和类组件来实现组件的,而 Vue 是通过对象来实现组件的。
- React 是通过状态提升来实现跨组件通信的,而 Vue 是通过事件总线来实现跨组件通信的。
- React 是通过 shouldComponentUpdate 或者 React.memo 来实现组件性能优化的,而 Vue 是通过 v-if 和 keep-alive 来实现组件性能优化的。
- React 是通过 React.lazy 和 Suspense 来实现代码分割的,而 Vue 是通过异步组件来实现代码分割的。