- react组件如何通讯
- JSX本质是什么
- context是什么,有何用途
- shouldComponentUpdate(SCU)的用途
- 描述redux单向数据流
- setState是同步还是异步
- 基于React设计一个todoList(组件结构,redux state数据结构)
基础回顾
JSX
jsx可以让我们直接在js代码里书写html(前提要引入React),例如
const JSXDemo = () => {
const data = {key: 'value'}
return (
<>
<div>this is jsx</div>
<div>key is {data.key}</div>
</>
)
}
条件渲染
类似javascript,可以有if else、三元表达式、短路表达式等 条件渲染
const ConditionDemo = () => {
const theme = 'black'
const blackBtn = <button className={style.blackBtn}>black btn</button>
const whiteBtn = <button className={style.whiteBtn}>white btn</button>
// if else
if(theme === 'black') {
return whiteBtn
} else {
return blackBtn
}
// 在jsx中使用三元表达式
return (
<>
{
theme === 'black' ? blackBtn : whiteBtn
}
</>
)
// jsx中使用短路表达式
return (
<>
{theme === 'black' && blackBtn}
</>
)
}
列表渲染
在jsx中渲染list,直接将含有jsx的数组进行渲染即可,例如
const ListDemo = () => {
const originList = [
{
id: 'id-1',
title: '标题1'
},
{
id: 'id-2',
title: '标题2'
},
{
id: 'id-3',
title: '标题3'
}
];
const [list] = useState(originList)
return (
<>
<ul>
{/* arr.map产出一个jsx的数组即可在页面上渲染 */}
{list.map((item, index) => <li key={item.id}>index: {index}; title: {item.title}</li>)}
</ul>
</>
)
}
事件绑定
react的事件绑定中有this指向问题,不过这是针对class component来说的,默认的this是undefined。其中的event对象不是原生,而且在react中事件的target和currentTarget不同,后者是document,即react中所有的事件都挂载在document上。
export default class EventDemo extends React.Component {
constructor(props) {
super(props)
// 这种效率最高,因为bind只执行一次
this.handleClick2.bind(this)
}
handleClick1(arg) {
console.log(arg)
}
handleClick2(e, arg) {
console.log(e.target, arg)
}
handleClick3 = (e, args) => {
console.log(e.target, args);
// <butto> event 2</button> #document
console.log(e.nativeEvent.target, e.nativeEvent.currentTarget);
}
render() {
return (
<>
{/* 每次点击都会返回一个新的函数 */}
<button onClick={this.handleClick1.bind(this, 'args')}>event 1</button>
<button onClick={e => this.handleClick2(e, 'args')}> event 2</button>
<button onClick={e => this.handleClick3(e, 'args')}>event 3</button>
</>
)
}
}
综上得出:
- event是合成事件,模拟出DOM事件的全部功能
- event.nativeEvent是原生事件对象
- 所有的事件都挂载到了document上
- 和DOM事件不一样,和Vue事件也不一样
组件通讯
父子组件一般通过props进行通信(层级不深的时候)
import React, { useState } from 'react'
import PropTypes from 'prop-types'
const Input = ({ addListItem, length }) => {
const [value, setValue] = useState('')
const onInputChange = e => setValue(e.target.value)
const submit = () => {
if (value) {
addListItem({ id: length + 1, title: value })
setValue('')
}
}
const onKeyUp = e => {
if (e.which === 13) {
submit()
}
}
return (
<>
<input value={value} onChange={onInputChange} onKeyUp={onKeyUp} />
<button onClick={submit}>提交</button>
</>
)
}
const List = ({ list, removeItem }) => {
return <ul>{list.map((item, index) => <li onClick={() => removeItem(item.id)} key={item.id}><span>{index}.{item.title}</span></li>)}</ul>
}
List.propTypes = {
list: PropTypes.array.isRequired,
removeItem: PropTypes.func.isRequired
}
export default () => {
const [list, setList] = useState([
{ id: 1, title: '标题1' }
])
const addListItem = item => {
const newList = [...list, item]
setList(newList)
}
const removeItem = id => {
const newList = list.filter(item => item.id !== id)
setList(newList)
}
return (
<>
<Input addListItem={addListItem} length={list.length} />
<List list={list} removeItem={removeItem} />
</>
)
}
关于useState中的set方法和class component中的setState,均为不可变值,只能通过调用setState等赋值新的值或地址,可能是异步更新,也可能会被合并(一次性多次set)
举个例子
export default class StateDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
count: 0
}
}
increase = () => {
// setCount(count + 1)
// console.log(count); // 这里拿不到最新的值
// 多次设置,直接传入对象,合并为一次
// const count = this.state.count
// this.setState({ count: count + 1 })
// this.setState({ count: count + 1 })
// this.setState({ count: count + 1 })
// 定时器中setState是同步的
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log('count in setTimeout', this.state.count)
}, 0);
// 自己定义的DOM事件,setState是同步的
// 见componentDidMound
// 多次设置,传入函数时,不合并,同步执行
// const count = this.state.count
// this.setState((prevState, props) => ({ count: prevState.count + 1 }))
// this.setState((prevState, props) => ({ count: prevState.count + 1 }))
// this.setState((prevState, props) => ({ count: prevState.count + 1 }))
// 同步
// Promise.resolve().then(() => {
// this.setState({
// count: this.state.count + 1
// })
// console.log(this.state.count);
// })
}
componentDidMount(){
// 自己定义的DOM事件,setState是同步的
document.body.addEventListener('click', () => {
this.setState({count: this.state.count + 1})
console.log(this.state.count)
})
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={this.increase}>累加</button>
</div>
)
}
}
总的来说,setState是异步还是同步基于设置时的方式:
setState时位于基于event loop形式的回调函数里,则为同步,反之为异步(注意:如果setState传入函数时,是同步)
生命周期
单组件生命周期
父子组件生命周期
和vue类似:
- 创建阶段:父 -> 子
- 渲染阶段:componentWillMount 父 -> 子, componentDidMount 子 -> 父
- 更新阶段:父组件更新数据,子组件更新数据;componentDidUpdate 子 -> 父
- 卸载阶段:componentDidUnMount 子 -> 父
高级特性
函数组件
函数组件不需要去理解class的生命周期,是一个纯函数,输入一个props,输出jsx。函数组件
import React, { Fragment, useState, useCallback, useEffect, useContext } from 'react'
// redux中还有useSelector useDispatch
// react-router还有useLocation、useHistory等hooks
export default () => {
return (
<Fragment>
</Fragment>
)
}
react函数组件可以使用hook,来管理数据和上下文等。这些后面介绍
非受控组件
非受控组件使用场景:
- 必须手动操作dom元素,setState实现不了
- 文件上传
<input type="file">
- 某些富文本编辑器,需要传入dom元素
优先使用受控组件,必须操作DOM时,使用非受控组件
Portals(传送门)
有的时候,需要将子组件渲染到父组件外部(例如模态框、弹出层等),这个时候就需要使用Portals
import React, { Fragment } from 'react'
import ReactDOM from 'react-dom'
import style from './index.module.css'
export default ({ children }) => {
// 将子组件渲染到body,避免弹出层嵌套层级过深
return ReactDOM.createPortal(<div className={style.modal}>{children}</div>, document.body)
// return (
// <Fragment>
// <div className={style.modal}>
// {children}
// </div>
// </Fragment>
// )
}
context
context一般用于跨组件通讯,如果用redux的话小题大做。context有关的api有:useContext、createContext,一般和useReducer配合来完成类似redux的数据管理
import React, { Fragment, createContext, useContext, useReducer } from 'react'
const defualtTheme = 'light'
const THEME = 'THEME'
const themeCtx = createContext(defualtTheme)
function ThemeReducer(state, action) {
switch (action.type) {
case THEME:
// 因为reducer是纯函数,所以每次数据的改动都必须返回一个新的对象
return { ...state, theme: action.payload }
default:
return state
}
}
const Link = () => {
// 子组件获取数据
const { value } = useContext(themeCtx)
return (
<div>{value.theme}</div>
)
}
export default () => {
const [reducer, dispatch] = useReducer(ThemeReducer, { theme: defualtTheme })
const changeTheme = () => {
dispatch({ type: THEME, payload: 'dark' })
}
return (
<Fragment>
{/* 传递数据到子组件 */}
<themeCtx.Provider value={{ dispatch, value: reducer }}>
<Link />
<button onClick={changeTheme}>切换主题</button>
</themeCtx.Provider>
</Fragment>
)
}
异步组件
和Vue一样,React中也是通过import()
进行动态加载组件的,不过这个不常用,最常用的是React.lazy配个React.Suspense配合使用
import React, { Fragment } from 'react'
// import() 方式
// const load = () => import('./ContextDemo')
// export default () => {
// const loadComponent = () => {
// load()
// }
// return (
// <Fragment>
// <button onClick={loadComponent}>点击加载组件</button>
// </Fragment>
// )
// }
// 异步加载组件React.lazy和React.suspense
// function showImport(fn, delay) {
// return new Promise(resolve => {
// setTimeout(() => resolve(fn), delay)
// })
// }
const Component = React.lazy(() => import('./ContextDemo.js'))
export default () => {
return (
<React.Suspense fallback="加载中....">
<Component />
</React.Suspense>
)
}
性能优化
React中父组件更新,子组件无条件也更新;SCU不建议使用深度比较
- shouldComponentUpdate for class component, useEffect for functional component(类组件使用SCU进行优化,函数组件使用useEffect)
- PureComponent(浅层比较,可使用immutable.js进行控制)、React.memo(hook)
- 使用immutable.js
问题:
- 为什么SCU默认返回true,而要提供PureComponent供开发人员使用,为什么不默认就是PureComponent的形式?
因为SCU只有在需要的时候需要,将优化的权力交给开发;
组件抽离
- mixin,已被react废弃
- 高阶组件HOC
- Render Props
- useHook
高阶组件HOC
高阶组件并非是一种功能,而是一种模式。相当于高阶函数(或装饰器)。在redux中,connect就是一个高阶组件
import React, { Component } from 'react'
// 高阶组件,
const withComponent = (Comp) => {
return class withComponent extends Component {
constructor(props) {
super(props)
this.state = {
x: 0,
y: 0
}
}
handleMouseMove = e => {
this.setState({ x: e.clientX, y: e.clientY })
}
render() {
return (
<div
onMouseMove={this.handleMouseMove}
style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}>
<Comp mousePosition={this.state} {...this.props} />
</div>
)
}
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {}
}
render() {
return (<div>x:{this.props.mousePosition.x}, x:{this.props.mousePosition.y}, </div>);
}
}
export default withComponent(App)
Render Props
Render Props通过一个函数将class的state作为props传递给纯函数组件,与高阶组件相反,它是将内部的state传递到外部进行渲染。
import React, { Component } from 'react'
import PropTypes from 'prop-types'
class Factory extends Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0
}
}
handleMouseMove = e => {
this.setState({ x: e.clientX, y: e.clientY })
}
render() {
return (<div onMouseMove={this.handleMouseMove}>{this.props.render(this.state)}</div>);
}
}
Factory.prototypes = {
render: PropTypes.func.isRequired
}
const App = () => {
return (
<Factory render={props => <div style={{ height: 500, width: 500, background: '#ccc', color: '#fff' }}>x: {props.x}, y:{props.y}</div>}></Factory>
)
}
export default App
HOC vs Render Props:
- HOC: 模式简单,但是会增加组件嵌套层级,代码比较难看懂
- Render Props:代码简洁,学习成本较高
Redux
Redux是一个javascript的数据或状态state管理框架,并不但用于react,但是却完美结合了react关于不可变值和纯函数的理念。
基本概念
store、action、reducer
单向数据流
概述:dispatch(action) -> reducer -> newState -> subscribe触发订阅 -> 更新视图
中间件
react-redux:Provider、connect、mapStateToProps、mapDispatchToProps、useSelector、useDispatch、useStore
异步action
使用redux-thunk
react-router
// Router.js
import React, { Fragment } from 'react'
import Home from './Home'
import NotFound from './NotFound'
import Project from './Project'
import { Route, HashRouter, Switch, Redirect } from 'react-router-dom'
export default () => {
return (
<Fragment>
<HashRouter>
<Switch>
{/* 匹配路由 */}
<Route exact path="/" component={Home} />
<Route path="/project/:id" component={Project} />
<Route path="/notfound" component={NotFound} />
<Redirect from="*" to='/notfound' />
</Switch>
</HashRouter>
</Fragment>
)
}
// Project.js
import React, { Fragment } from 'react'
import { useParams, useHistory } from 'react-router-dom'
export default () => {
const params = useParams()
const history = useHistory()
return (
<Fragment>
this is Project, project id is {params.id}
<button onClick={() => history.push('/')}>go to Home</button>
</Fragment>
)
}