七、React使用
React基本使用
1.JSX基本使用
变量、表达式
class style
子元素和组件
import React from 'react'
import './style.css'
import List from '../List'
class JSXBaseDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
name: '双越',
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
2.条件判断
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
3.渲染列表
map:map返回一个重组数组
key
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
4.事件(重点)
bind this
关于event参数(clickHandler3示例很重要)
传递自定义参数
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 指向,在这边bind初始化时一次就好,性能好.
this.clickHandler1 = this.clickHandler1.bind(this)
// 如果在下面使用:onClick={this.clickHandler1.bind(this)}就会在点击DOM上bind每次点击都要bind一次
}
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 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
}
// 传递参数
clickHandler4(id, title, event) {
console.log(id, title)
console.log('event', event) // 最后追加一个参数,即可接收 event
}
}
export default EventDemo
5.表单
受控组件:input的值受state影响
input textarea select 用valuei
checkbox radio 用 checked
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
6.组件使用
props传递数据
props传递函数
props类型检查
/**
* @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 = () => {
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
7.setState(考察的重点--重中之重)
不可变值(setstate不能提前对state值进行修改,应该什么时候改就什么时候设值,而且设置的时候不能影响之前state的值)
可能是异步更新(直接使用是异步的,在setTimeout 和自定义DOM 事件中 是同步的)
可能会被合并
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
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 异步更新的话,更新前会被合并 -------------------------
// componentDidMount(){
// document.body.addEventListener('click',()=>{
// this.setState({
// count:this.state.count+1
// })
// console.log(this.state.count,'count in body event')
// })
// }
// // 传入对象,会被合并(类似 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 进行属性设置,这样违反不可变值
8.组件生命周期--必考
单组件生命周期
React 组件生命周期图示
projects.wojtekmaj.pl/react-lifec…
父子组件生命周期,和Vue的一样
React高级特性
1.函数组件
纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法
2.非受控组件
ref
defaultValue defaultChecked
手动操作DOM元素
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
使用场景
必须手动操作DOM元素,setState实现不了
文件上传<input type = file>
某些富文本编辑器,需要传入DOM元素
受控组件 vs 非受控组件
优先使用受控组件,符合React设计原则
必须操作DOM时,再使用非受控组件
3.Portals 传送门
组件默认会按照既定层次嵌套渲染
如何让组件渲染到父组件以外?
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
使用场景
overflow:hidden(父组件bfc,限制子组件展示)
父组件z-index值太小(让子组件渲染逃离父组件)
fixed需要放在body第一层级(让子组件渲染逃离父组件)
4.context
公共信息(语言、主题)如何传递给每个组件
用props太繁琐
用redux小题大做
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
5.异步组件(懒加载)
import()
React.lazy
React.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
强制刷新,可看到 loading (看不到就限制一下 chrome 网速)
看 network 的 js 加载,会单独有个js
6.性能优化(重点)
性能优化,永远都是面试的重点
性能优化对于React更加重要
回顾讲setState时重点强调的不可变值
shouldComponentUpdate(简称SCU)
React默认:父组件有更新,子组件无论数据是否改变则无条件也更新
SCU默认返回true
/**
* @description 演示 props 和事件
* @author 双越老师
// frame-project-interview/react-code-demo/src/components/baseUse/PropsDemo.js
*/
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
SCU一定要配合不可变值
// react-code-demo/src/components/advancedUse/SCUDemo2.js
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 做对象或者数组的深度比较(一次性递归到底),深度比较较耗费性能,可以使用浅比较,设计state层级尽量设计得不要太深,扁平一些
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 ,故意写的错误用法,会导致SCU不执行,因为state值push后和setState一致
// this.state.list.push({
// id: `id-${Date.now()}`,
// title
// })
// this.setState({
// list: this.state.list
// })
}
}
export default TodoListDemo
SCU使用总结
SCU默认返回true,即React默认重新渲染所有子组件
必须配合“不可变值”一起使用
可先不用SCU,有性能问题时再考虑使用
PureComponent和React.memo
PureComponent,SCU中实现了浅比较
memo,函数组件中的PureComponent
浅比较已适用大部分情况(尽量不要深度比较)
//PureComponentDemo.js
//react-code-demo/src/components/advancedUse/PureComponentDemo.js
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.js
彻底拥抱“不可变值”
基于共享数据(不是深拷贝),速度好
有一定学习和迁移成本,按需使用
7.高阶组件HOC
关于组件公共逻辑的抽离
mixin,已被React弃用
高阶组件HOC
Render Props
// react-code-demo/src/components/advancedUse/HOCDemo.js
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源码
提问:Vue如何实现高阶组件?
8.Renders Props
// react-code-demo/src/components/advancedUse/RenderPropDemo.js
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
HOC:模式简单,但会增加组件层级
Render Props:代码简洁,学习成本较高
按需使用
Redux
和Vuex作用相同,但比Vuex学习成本高
不可变值,纯函数
面试常考
Redux使用
基本概念
store state
action
reducer
//https://www.redux.org.cn/
import { createStore } from 'redux';
/**
* 这是一个 reducer,形式为 (state, action) => state 的纯函数。
* 描述了 action 如何把 state 转变成下一个 state。
*
* state 的形式取决于你,可以是基本类型、数组、对象、
* 甚至是 Immutable.js 生成的数据结构。惟一的要点是
* 当 state 变化时需要返回全新的对象,而不是修改传入的参数。
*
* 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
* 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
*/
function counter(state = 0, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
}
// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);
// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
console.log(store.getState())
);
// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1
单项数据流
dispatch(action)
reducer -> newState
subscribe触发通知
dispatch一个action会触发reducer,reducer更新状态,注意不可变值,再触发订阅通知,渲染到页面
react-redux
Provider
connect
mapStateToProps mapDispatchToProps
//index.js
import React from 'react'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'
let store = createStore(todoApp)
export default function () {
return <Provider store={store}>
<App />
</Provider>
}
//AddTodo.js
import React from 'react'
import { connect } from 'react-redux'
import { addTodo } from '../actions'
// 函数组件,接收 props 参数
let AddTodo = ({ dispatch }) => {
// dispatch 即 props.dispatch
let input
return (
<div>
<form
onSubmit={e => {
e.preventDefault()
if (!input.value.trim()) {
return
}
// 创建一个 todo
dispatch(addTodo(input.value))
input.value = ''
}}
>
<input
ref={node => {
input = node
}}
/>
<button type="submit">
Add Todo
</button>
</form>
</div>
)
}
// connect 高阶组件 ,将 dispatch 作为 props 注入到 AddTodo 组件中
AddTodo = connect()(AddTodo)
export default AddTodo
//VisibleTodoList.js
import { connect } from 'react-redux'
import { toggleTodo } from '../actions'
import TodoList from '../components/TodoList'
// 不同类型的 todo 列表
const getVisibleTodos = (todos, filter) => {
switch (filter) {
case 'SHOW_ALL':
return todos
case 'SHOW_COMPLETED':
return todos.filter(t => t.completed)
case 'SHOW_ACTIVE':
return todos.filter(t => !t.completed)
}
}
const mapStateToProps = state => {
// state 即 vuex 的总状态,在 reducer/index.js 中定义
return {
// 根据完成状态,筛选数据
todos: getVisibleTodos(state.todos, state.visibilityFilter)
}
}
const mapDispatchToProps = dispatch => {
return {
// 切换完成状态
onTodoClick: id => {
dispatch(toggleTodo(id))
}
}
}
// connect 高阶组件,将 state 和 dispatch 注入到组件 props 中
const VisibleTodoList = connect(
mapStateToProps,
mapDispatchToProps
)(TodoList)
export default VisibleTodoList
异步action
中间件有:
redux-thunk
redux-promise
redux-saga
中间件
Redux知识总结
基本概念
单项数据流
react-redux
异步action
中间件
React-router
面试考点并不多(前提是熟悉React)
路由模式(hash,H5 history),同vue-router
路由配置(动态路由、懒加载),同vue-router
路由模式
hash模式(默认),如abc.com/#/user/10\ H5 history模式,如abc.com/user/20\ 后者需要server端支持,因此无特殊需求可选择前者
toC客户端一般H5 history,toB控制后台一般hash
React-router总结
路由模式(hash、H5 history)
路由配置(动态路由、懒加载)
掌握基本使用
八、React原理
函数式编程
一种编程范式,概念比较多
纯函数
不可变值
回顾vdom和diff
h函数
vnode数据结构
patch函数
只比较同一层级,不跨级比较
tag不相同,则直接删掉重建,不再深度比较
tag和key,两者都相同,则认为是相同节点,不再深度比较
Vue2.x Vue3.0 React三者实现vdom细节都不同
核心概念和实现思路,都一样
面试主要考察后者,不用全部掌握细节
JSX本质
JSX等同于Vue模板
Vue模板不是html
JSX也不是JS
// 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
React.createElement即h函数,返回vnode
第一个参数,可能是组件,也可能是html tag
组件名,首字母必须大写(React规定)
合成事件
所有事件挂载到document上
event不是原生的,是SyntheticEvent合成事件对象
和Vue事件不同,和DOM事件也不同
// 1. event 是 SyntheticEvent ,模拟出来 DOM 事件所有能力
// 2. event.nativeEvent 是原生事件对象
// 3. 所有的事件,都被挂载到 document 上
// 4. 和 DOM 事件不一样,和 Vue 事件也不一样
为何要合成事件机制
更好的兼容性和跨平台
载到document,减少内存消耗,避免频繁解绑
方便事件的统一管理(如事务机制)
setState batchUpdate
有时异步(普通使用),有时同步(setTimeout、DOM事件)
有时合并(对象形式),有时不合并(函数形式)
后者比较好理解(像Object.assign),主要讲解前者
核心要点
setState主流程
batchUpdate机制
setState是同步还是异步
setState无所谓异步还是同步
看是否能命中batchUpdate机制
判断isBatchingUpdates
哪些能命中batchUpdate机制
生命周期(和它调用的函数)
React中注册的时间(和它调用的函数)
React可以“管理”入口
哪些不能命中batchUpdate机制
setTimeout setInterval等(和它调用的函数)
自定义的DOM时间(和它调用的函数)
React“管不到”的入口
transaction(事务)机制
组件渲染过程
JSX如何渲染为页面
setState之后如何更新页面
面试考察全流程
回顾Vue组件渲染和更新过程
回顾JSX本质和vdom
JSX即createElement函数
执行生成vnode
patch(elem,vnode)和patch(vnode,newVnode)
回顾dirtyComponents
讲解内容
组件渲染过程
props state
render()生成vnode
patch(elem,vnode)
组件更新过程
setState(newState) --> dirtyComponents(可能有子组件)
render()生成newVnode
patch(vnode,newVnode)
更新的两个阶段
上述的patch被拆分为两个阶段:
reconciliation阶段 - 执行diff算法,纯JS计算
commit阶段 - 将diff结果渲染DOM
可能会有性能问题
JS是单线程,且和DOM渲染共用一个线程
当组件足够复杂,组件更新时计算和渲染都压力大
同事再有DOM操作需求(动画,鼠标拖拽等),将卡顿
解决方案 fiber
将reconciliation阶段进行任务拆分(commit无法拆分)
DOM需要渲染时暂停,空闲时恢复
window.requestIdleCallback
关于fiber
React内部运行机制,开发者体会不到
了解背景和基本概念即可
小结
组件渲染和更新过程
更新的两个阶段,reconciliation及commit
React fiber
九、React面试真题演练
1.组件之间如何通讯
父子组件props
自定义事件
Redux和Context
2.JSX本质是什么
createElement
执行返回vnode
3.Context是什么?如何应用
父组件,向其下所有子孙组件传递信息
对一些简单的公共信息:主题色、语言等
复杂的公共信息,请用redux
4.shouldComponentUpdate用途
性能优化
配合“不可变值”一起使用,否则会出错
5.redux单项数据流
6.setState场景题--类似几率很大
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
},0)
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('4', this.state.count) // 3
},0)
}
}
export default ListDemo
7.什么是纯函数
返回一个新值,没有副作用(不会“偷偷”修改其他值)
重点:不可变值
如arr1 = arr.slice()
8.React组件生命周期--非常重要必考
单组件生命周期
父子组件生命周期
注意SCU
9.React发起ajax应该在哪个生命周期
同Vue
componentDidMount
10.渲染列表,为何使用key--非常重要必考
同Vue,必须用key,且不能是index和random
diff算法中通过tag和key来判断,是否是sameNode
减少渲染次数,提升渲染性能
11.函数组件和class组件区别
纯函数,输入props,输出JSX
没有实例,没有生命周期,没有state
不能扩展其他方法
12.什么是受控组件
表单的值,受state控制
需要自行监听oChange,更新state
对比非受控组件
13.何时使用异步组件
同Vue
加载大组件
路由懒加载
14.redux如何进行异步请求
使用异步action
如redux-thunk
15.react-router如何配置懒加载
16.PureComponent有何区别
实现了浅比较的shouldComponentUpdate
优化性能
但要结合不可变值使用
17.React事件和DOM事件的区别
所有事件都挂载到document上
event不是原生的,是SyntheticEvent合成事件对象
dispatchEvent
18.React性能优化
渲染列表加key
自定义事件、DOM事件及时销毁
合理使用异步组件
减少函数bind this的次数
合理使用SCU PureComponent和memo
合理使用Immutable.js
webpack层面的优化(后面会讲)
前端通用的性能优化,如图片懒加载
使用SSR
19.React和Vue的区别
都支持组件化
都是数据驱动视图
都使用vdom操作DOM
React使用JSX拥抱JS,Vue使用模板拥抱html
React函数式编程,Vue声明式编程
React更多需要自力更生,Vue把想要的都给你
VUE 与 React 区别
React 的思路是 HTML in JavaScript 也可以说是 All in JavaScript,通过 JavaScript 来生成 HTML,所以设计了 JSX 语法,还有通过 JS 来操作 CSS,社区的styled-component、JSS等。
Vue 是把 HTML,CSS,JavaScript 组合到一起,用各自的处理方式,Vue 有单文件组件,可以把 HTML、CSS、JS 写到一个文件中,HTML 提供了模板引擎来处理。
React 整体是函数式的思想,在 React 中是单向数据流,推崇结合 immutable 来实现数据不可变。
而 Vue 的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立 Watcher 来监听,当属性变化的时候,响应式的更新对应的虚拟 DOM。
如上,所以 React 的性能优化需要手动去做,而Vue的性能优化是自动的,但是Vue的响应式机制也有问题,就是当 state 特别多的时候,Watcher 会很多,会导致卡顿。
React 与 VUE 共同点
React 与 Vue 存在很多共同点,例如他们都是 JavaScript 的 UI 框架,专注于创造前端的富应用。不同于早期的 JavaScript 框架“功能齐全”,Reat 与 Vue 只有框架的骨架,其他的功能如路由、状态管理等是框架分离的组件。
优势
React
- 灵活性和响应性:它提供最大的灵活性和响应能力。
- 丰富的JavaScript库:来自世界各地的贡献者正在努力添加更多功能。
- 可扩展性:由于其灵活的结构和可扩展性,React已被证明对大型应用程序更好。
- 不断发展: React得到了Facebook专业开发人员的支持,他们不断寻找改进方法。
- Web或移动平台: React提供React Native平台,可通过相同的React组件模型为iOS和Android开发本机呈现的应用程序。
Vue
- 易于使用: Vue.js包含基于HTML的标准模板,可以更轻松地使用和修改现有应用程序。
- 更顺畅的集成:无论是单页应用程序还是复杂的Web界面,Vue.js都可以更平滑地集成更小的部件,而不会对整个系统产生任何影响。
- 更好的性能,更小的尺寸:它占用更少的空间,并且往往比其他框架提供更好的性能。
- 精心编写的文档:通过详细的文档提供简单的学习曲线,无需额外的知识; HTML和JavaScript将完成工作。
- 适应性:整体声音设计和架构使其成为一种流行的JavaScript框架。
- 它提供无障碍的迁移,简单有效的结构和可重用的模板。
总结
如上所说的 Vue 的响应式机制也有问题,当 state 特别多的时候,Watcher 会很多,会导致卡顿,所以大型应用(状态特别多的)一般用 React,更加可控。
可对于易用性来说,VUE 是更容易上手的,对于项目来说新人更容易接手。
技术没有哪个更好或者是更优秀,只要适合自己的才是最合适的。