一、基本使用
1、create-react-app
2.JSX语法
(1)变量、表达式
// 获取变量 插值
const pElem = <p>{this.state.name}</p>
return pElem
// 表达式
const exprElem = <p>{this.state.flag ? 'yes' : 'no'}</p>
return exprElem
(2)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
(3)子元素和组件
// 子元素
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
3.条件
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>
4.列表渲染
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>
}
5.事件
(1)bind this 改变事件指向
this.clickHandler1 = this.clickHandler1.bind(this)
(2)关于event参数:
React16事件绑定到document上
1. event 是 SyntheticEvent ,模拟出来 DOM 事件所 有能力
2. event.nativeEvent 是原生事件对象
3. 所有的事件,都被挂载到 document 上
4. 和 DOM 事件不一样,和 Vue 事件也不一样
5.
React17及以后的版本事件绑定到root组件上
这样有利于多个React版本共存,例如微前端
(3)传递自定义参数
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
}
6.组件和props(类型检查)
(1)父组件将参数和方法直接绑定在子组件上,子组件使用props接受父组件传递过来的参数和方法
(2)props类型检查:
import PropTypes from 'prop-types'
Input.propTypes = {
submitTitle: PropTypes.func.isRequired//submitTitle是父组件传递过来的方法
}
7.state和setState
在类组件中使用state和setState,函数组件中没有。
(1)state:在constructor(构造函数)中定义。不要直接修改state,使用不可变值
(2)setState:
1️⃣不可变值:不能直接对 this.state.list 进行 push 、pop 、splice 等(对于数组我们可以使用cancat、filter、slice、扩展运算符或单独外部定义一个新数组处理后再在setState中赋值等方法进行修改)。不能直接对 this.state.obj 进行属性设置,这样违反不可变值这样违反不可变值,可以使用Object.assign或扩展运算符的形式进行对象的修改。
2️⃣可能是异步更新:
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 中
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)
}
3️⃣可能被合并
// 传入对象,会被合并(类似 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
}
})
8.组件和生命周期
单组件生命周期:
父子组件生命周期:当父组建 render 时遇到子组件,然后进入子组件的生命周期,当执行完子组件生命周期中的componentDidMount 时会回到父组建继续执行父组建未完成的生命周期。
二、高级特性
1、函数组件
纯函数,输入props 输出JSX,没有实例,没有生命周期,没有state
2、受控和非受控组件
(1)受控组件:表单里的值收到setState的控制(通过绑定onChange等事件)
(2)非受控组件:表单组件的值不受setState控制,不绑定onChange时间,而是同时绑定ref的方式对dom元素进行操作(应用场景:文件上传等)
3、refs
react18:
//函数组件
import {useRef} from 'react'
const inputRef=useRef(null)
<imput ref={inputRef}/>
react16:
//类组件
import React from 'react'
this.inputRef= React.createRef()
<input ref={this.inputRef}/>
4、Protals(传送门)
组件默认会按照既定层次嵌套渲染,如何让组件渲染到父组件以外?
使用场景:overflow:hidden,父组件z-index值太小、fixed需要放在body第一层级
import React from 'react'
import ReactDOM from 'react-dom'
import './style.css'//这里设置元素fixed定位
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
5、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
6、异步组件(懒加载)
import( )、React.lazy、React.Suspence
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
7、性能优化
1️⃣shouldComponantUpdate(SCU)
SCU默认返回true,即React默认重新渲染所有子组件,必须配合“不可变值”一起使用,可先不用SCU,有性能问题时再考虑使用
当组件内部其他子组件更新时,无需更新的组件也会发起更新,如果引起页面卡顿则可以使用SCU钩子,判断nextProps和目前传入的props内容是否一致,如果需要更新则返回true 否则返回false
import React from 'react'
import _ from 'lodash'
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 // 不重复渲染
// }
// 增加 shouldComponentUpdate这里_是引入的lodash
shouldComponentUpdate(nextProps, nextState) {
// _.isEqual 做对象或者数组的深度比较(一次性递归到底)
if (_.isEqual(nextProps.list, this.props.list)) {
// 相等,则不重复渲染
return false
}
return true // 不相等,则渲染
}
// React 默认:父组件有更新,子组件则无条件也更新!!!
// 性能优化对于 React 更加重要!
// SCU 一定要每次都用吗?—— 需要的时候才优化
}
2️⃣纯组件PureComponant和memo
(1)PureComponant:
创建类组件的时候使用React.PureComponent,相当于做了一个SCU的浅比较
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
}
(2)memo:
function MyComponent(props){
return <div>{props.text}</div>
}
function areEqual(preProps,nextProps){
//比较preProps和nextProps是否一致,一致则返回true ,否则返回false
}
export default React.memo(MyComponent,areEqual)
3️⃣不可变值immutablejs
基于共享数据(不是深拷贝),速度好,有一定的学习和迁移成本,按需使用
const map1=Immutable.Map({a:1,b:2,c:3})
const map2=map1.set('b',50)
map1.get('b') //2
map2.get('b') //50
11、高阶组件HOC
高阶组件不是一种功能,而是一种模式,在高阶组件里定义一些公共逻辑,来包裹有需要的组件,通过属性绑定的方式传到子组件中来。
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) // 返回高阶函数
12、Render Props
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
三、周边工具
1、redux :
1️⃣store2️⃣reducer3️⃣action4️⃣dispatch
import { createStore } from 'redux'
/**
* 这是一个 reducer 函数:接受当前 state 值和描述“发生了什么”的 action 对象,它返回一个新的 state 值。
* reducer 函数签名是 : (state, action) => newState
*
* Redux state 应该只包含普通的 JS 对象、数组和原语。
* 根状态值通常是一个对象。 重要的是,不应该改变 state 对象,而是在 state 发生变化时返回一个新对象。
*
* 你可以在 reducer 中使用任何条件逻辑。 在这个例子中,我们使用了 switch 语句,但这不是必需的。
*
*/
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case 'counter/incremented':
return { value: state.value + 1 }
case 'counter/decremented':
return { value: state.value - 1 }
default:
return state
}
}
// 创建一个包含应用程序 state 的 Redux store。
// 它的 API 有 { subscribe, dispatch, getState }.
let store = createStore(counterReducer)
// 你可以使用 subscribe() 来更新 UI 以响应 state 的更改。
// 通常你会使用视图绑定库(例如 React Redux)而不是直接使用 subscribe()。
// 可能还有其他用例对 subscribe 也有帮助。
store.subscribe(() => console.log(store.getState()))
// 改变内部状态的唯一方法是 dispatch 一个 action。
// 这些 action 可以被序列化、记录或存储,然后再重放。
store.dispatch({ type: 'counter/incremented' })
// {value: 1}
store.dispatch({ type: 'counter/incremented' })
// {value: 2}
store.dispatch({ type: 'counter/decremented' })
// {value: 1}
5️⃣单向数据流模型
dispatch调用action=>reducer更新newState=>subcribe触发通知更新视图
6️⃣中间件redux-thunk、redux-saga
用于改造dispatch方法
2、react-redux
1️⃣provider
<Provider store={store}>
<App />
</Provider>
2️⃣connect(mapStateToProps,mapDispatchToProps)
React-Redux提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来。
mapStateToProps:更新props—>作为输入源。返回一个对象,key为UI界面对应的名称,value为state处理的结果
mapDispatchToProps:更新action—>作为输出源。触发action更新reducer,进而更新state,从而驱动参数变化,引起UI数据的变化
import React, {Component} from 'react'
import {connect} from "react-redux";
class App extends Component {
render() {
const {value, onIncreaseClick, onReduceClick} = this.props
return (
<div>
<button style={{width: 40, height: 40}} onClick={onIncreaseClick}>+</button>
<text style={{padding: 40}}>{value}</text>
<button style={{width: 40, height: 40}} onClick={onReduceClick}>-</button>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
value: state.count
}
}
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onIncreaseClick: () => dispatch({type: 'increase'}),
onReduceClick: () => dispatch({type: 'reduce'})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App);
3、react-router
路由模式:hash/history
路由配置:动态路由 懒加载 Suspense lazy
四、原理
1、函数式编程
纯函数 、不可变值
2、vdom和diff算法
vdom:h函数 、vnode数据结构、 patch函数
diff : 只比较同一层级不跨级比较,tag不同则直接删掉重建,不再深度比较。tag和key两者都相同则认为是相同节点,不再深度比较
3、JSX本质
JSX等同于Vue的模板
JSX的本质是JavaScript的语法扩展,它提供了一种类似于HTML的语法来编写React组件,但实际上这些HTML-like的语法最终会被转换为JavaScript的React.createElement函数调用生成vnode后再通过patch函数进行渲染。
4、合成事件
React16:
更好的兼容性和跨平台、
挂载到document上(react17后挂载到root上),减少内存消耗,避免频繁解绑
方便事件的统一管理
React17及之后:
事件绑定到root组件上,有利于多个React版本并存,例如微前端
5、setState和batchUpdate
setState无所谓异步还是同步,关键看是否能命中batchUpdate机制,判断isBatchingUpdate是否为true
哪些能命中batchUpdate机制(异步)?
生命周期、react中注册的事件、react可以管理的入口
哪些不能命中batchUpdate机制(同步)?
setTimeOut setInterval、自定义dom时间、react管理不到的入口
6、组件渲染过程
jsx及createElement函数执行生成vnode,patch(elem,vnode)和patch(vnode,newVnode)
组件渲染:props state=>render()生成vnode=> patch(elem,vnode)
组件更新:setState(newState)=>dirtyComponents=>render()生成newVnode=> patch(vnode,newVnode)