setState()
函数用于更新组件的状态,有对象式和函数式两种写法。javascript
// 对象式
this.setState({
count: this.state.count + 1
})
// 函数式
this.setState((state, props) => {
count: state.count + 1
})
复制代码
函数式写法能够直接从参数接收到当前组件的 state
和 props
,更方便对状态进行处理。不管哪一种写法,setState()
的第二个参数均可以接收一个回调函数。css
this.setState({
count: this.state.count + 1
}, () => { // 回调函数
console.log(this.state.count)
})
复制代码
setState()
对状态的更新是异步的,回调函数会在状态变动完成后触发。也就是说若是直接在 setState()
调用以后去输出一个状态值,那么颇有可能拿到的仍是变动以前的值。java
Hooks 是 16.8
版本开始推出的针对函数式组件的功能。函数式组件没有 this
,因此没法操做 state
、refs
和生命周期钩子,而 Hooks 解决了这些问题。实际使用来看,并无类组件写着舒服,因此仍是老实用类组件吧!react
export default function Demo() {
const [name, setName] = React.useState('zidu')
changeName() {
// 操做 state
setName('CallbackZidu')
}
return (
<div> <h2>用户名:{name}</h2> <button onClick={changeName}>更名</button> </div>
)
}
复制代码
使用 React.useState()
能够建立出一个 state
,示例中使用的是数组解构赋值语法,name
就是这个状态的名字,setName
就是操做这个状态的函数。若是你有多个状态须要维护,就须要屡次使用 React.useState()
来建立这些状态,固然你也能够直接建立一个对象类型的状态来统一管理,可是这里有个大坑。编程
经过 React.useState()
拿到的 setXxx
函数对于状态改变的处理逻辑是不一样于 setState()
函数的。在 setState()
函数中容许只改变多个状态中某些状态的值。也就是说 setState()
函数拿到返回的对象后会和以前的 state
进行合并。可是 setXxx
不会合并,而是作替换操做。数组
const [all, setAll] = React.useState({
name: 'zidu',
age: 27
})
setAll({
// 尽管不变动 name 的值,可是这里仍是要用原来的值进行赋值,不然函数执行完后 name 就不存在了。
name: all.name,
age: 28
})
复制代码
Effect Hook 就是使用函数来模拟组件的生命周期,可是也只能模拟出三个生命周期,并且为了区别出不一样的生命周期,写法有点绕。服务器
React.useEffect(() => {
console.log('只传一个函数,模拟 componentDidUpdate 生命周期钩子')
})
复制代码
若是 React.useEffect()
只接收到了一个箭头函数做为参数,那么箭头函数就是 componentDidUpdate
生命周期钩子。markdown
React.useEffect(() => {
console.log('第二个参数是空数组,模拟 componentDidUpdate 生命周期钩子')
}, [])
复制代码
若是接收到的第二个参数是空数组,那么箭头函数就是 componentDidMount
生命周期钩子。react-router
React.useEffect(() => {
console.log('数组有元素,模拟 componentDidUpdate 和 componentDidUpdate 两个生命周期钩子')
}, [name])
复制代码
若是若是接收到的第二个参数不是空数组,那么箭头函数就是 componentDidMount
生命周期钩子,而且在每次 name
状态的值发生变化时做为 componentDidUpdate
生命周期钩子被触发。dom
React.useEffect(() => {
// do something...
return () => {
console.log('返回一个箭头函数,模拟 componentWillUnmount 生命周期钩子')
}
}, [name])
复制代码
若是在第一个参数里返回箭头函数,那么返回的箭头函数就是 componentWillUnmount
生命周期钩子。
Refs Hook 的使用和 React.createRef()
的用法是同样的。
export default function demo() {
// 建立一个 Ref 钩子
const myRef = React.useRef()
function getText() {
alert(myRef.current.value)
}
return (
<div> <input type="text" ref={myRef} /><br/> </div>
)
}
复制代码
组件懒加载能够避免在第一次打开网页时就把暂时尚未用到的组件都加载完毕,这样能够提升页面加载速度,组件会在真正须要渲染时才会被加载。
建立 A 组件:
export default class A extends Component {
render() {
return (
<div> <h2>我是 A</h2> </div>
)
}
}
复制代码
建立 B 组件:
export default class B extends Component {
render() {
return (
<div> <h2>我是 B</h2> </div>
)
}
}
复制代码
建立一个 Loading 组件,做用下面说:
export default class Loading extends Component {
render() {
return (
<div> <h2 style={{backgroundColor: 'orange'}}>Loding...</h2> </div>
)
}
}
复制代码
import React, { Component, lazy, Suspense } from 'react'
import {NavLink, Route} from 'react-router-dom'
// 同步加载 Loading 组件
import Loading from './Loading'
// 懒加载 A 和 B
const A = lazy(() => import('./A'))
const B = lazy(() => import('./B'))
export default class Main extends Component {
render() {
return (
<div> <ul> <NavLink to="/a">Home</NavLink> <NavLink to="/b">About</NavLink> </ul> <hr/> <Suspense fallback={<Loading/>}> <Route path="/a" component={A} /> <Route path="/b" component={B} /> </Suspense> </div>
)
}
}
复制代码
lazy()
函数用于实现组件的懒加载,接收一个函数提供要加载的组件,import()
导入函数和关键字 import
的做用是同样的。须要注意的是使用了懒加载后就必需要使用 <Suspense>
组件对懒加载组件进行包裹,而且经过 fallback
属性指定若是懒加载组件加载过程当中和加载失败时的兜底显示组件,也就是示例中的 Loading
组件,而且这个组件只能同步加载。
<Fragment>
实际是一个占位组件,在编码时看上去是一个组件,但在渲染时会被忽略。好比咱们一般在 render()
的返回内容最外层套一个 <div>
标签,若是你不想渲染出来的结构有太多没必要要的 <div>
,那么就能够用 <Fragment>
来替换。还有在遍历渲染列表时,也能够用来替代没必要要的结构。
import React, { Component, Fragment } from 'react'
export default class FragmentDemo extends Component {
render() {
return (
<Fragment> <h2>Fragment 标签在编译时会被丢弃掉,能够避免多组件时嵌套出太多层 div</h2> <h2>Fragment 能够接收一个 key 属性,这在遍历的时候能够用,避免产生太多外层元素</h2> </Fragment>
)
}
}
复制代码
以前在建立类式组件时都是继承 React.Component
,当多个组件嵌套而且子组件使用了父组件经过 props
传递过来的值时,若是父组件改变了 state
,不管子组件使用的那个状态是否出现变更,子组件都会随着父组件的渲染被从新渲染。
import React, { Component } from 'react'
export default class A extends Component {
state = {
name: 'zidu',
age: 27
}
changeName = () => {
this.setState({
name: 'CallbackZidu'
})
}
render() {
console.log('组件 A 发生渲染')
const {name, age} = this.state
return (
<div> <h2>组件 A</h2> <h2>名字:{name}</h2> <br/> <button onClick={this.changeName}>更名</button> <hr/> <B age={age}/> </div>
)
}
}
class B extends Component {
render() {
console.log('组件 B 发生渲染')
return (
<div> <h2>组件 B</h2> <h2>年龄:{this.props.age}</h2> </div>
)
}
}
复制代码
很明显这种子组件的被动渲染是没有必要的,这时候就可让子组件继承 React.PureComponent
来避免这种状况发生。React.PurComponent
实际重写了 shouldComponentUpdate
生命周期钩子,在内部判断了 state
和 props
是否发生变更,以此决定是否容许组件执行状态更新。为了方便起见,全部组件均可以去继承 React.PurComponent,而不用再继承 React.Component。
名称有点高端,看着有点懵逼,实际上就是和 Vue 的 Slot 同样的插槽技术,只是语法不一样而已。直接看示例:
import React, { PureComponent } from 'react'
import OtherComponent from '../OtherComponent'
export default class RenderPropsDemo extends PureComponent {
render() {
return (
<div> <h2>这是 RenderPropsDemo 组件</h2> {/* 经过传入一个函数返回要插入的组件 */} <A render={() => <OtherComponent/>}/> </div>
)
}
}
/** * 使用 this.props.render() 在 JSX 中预留要插入其余 DOM 的位置 * 相似 Vue 的 Slot 技术 * 函数名 render 是自定义的,改什么名字均可以 */
class A extends PureComponent {
render() {
return (
<div> <h2>这是 A 组件</h2> {this.props.render()} </div>
)
}
}
复制代码
在须要插入其余组件的地方直接写 this.props.render()
便可,以后插入的组件就会出如今这个位置。固然这个 props
上的 render
名字能够随便改,一般都叫这个。插入组件的时候直接在 props
上传入一个箭头函数,返回要插入组件的标签便可。
父子组件之间通讯能够走 props
,兄弟组件之间通讯能够走发布订阅,那么祖孙组件之间通讯就能够用 Context,固然用发布订阅也能够,并且更舒服。
好比如今 A 组件要把一些数据传递给 C 组件使用,那就可使用 React.createContext()
建立一个 Context。
import React, { Component } from 'react'
import './index.css'
// 建立一个 Context
const NameContext = React.createContext()
export default class ContextDemo extends Component {
state = {
name: 'zidu',
age: 27
}
render() {
const {name, age} = this.state
return (
<div className="parent"> <h2>A 组件</h2> <h2>名称:{name}</h2> {/* 使用 Provider 包括下一级组件 */} <NameContext.Provider value={name}> <B /> </NameContext.Provider> </div>
)
}
}
class B extends Component {
render() {
return (
<div className="child"> <h2>B 组件</h2> <h2>显示点东西打酱油</h2> <C/> </div>
)
}
}
function C() {
return (
<div className="grand"> <h2>C 组件</h2> <h2>使用 Consumer 标签接收名称: {/* Consumer 内的箭头函数参数就是从 A 组件传递过来的数据 */} <NameContext.Consumer> { value => { return value.name } } </NameContext.Consumer> </h2> <D/> </div>
)
}
复制代码
C 组件接收的时候也能够不写 <Consumer>
标签,改用编程方式要简单一些:
class C extends Component {
// 声明要使用的 Context
static contextType = NameContext
render() {
return (
<div className="grand-grand"> <h2>C 组件</h2> <h2>使用 contextType 接收名称:{this.context.name}</h2> </div>
)
}
}
复制代码
Context 通常在封装组件时使用,写业务时较少用,了解一下就行。祖孙组件之间的通讯要么走 Redux,要么走发布订阅,用起来都比 Context 舒服。
子组件可能因为请求数据失败或者拿到的数据和约定的格式不符等状况致使渲染失败,这时候异常就会层层往外传递致使整个页面渲染失败出现错误信息。错误边界就是将子组件的渲染错误限制在必定范围内,并使用特定内容去替换错误信息。
好比在 A 组件里使用了 B 组件,为了不 B 出现渲染失败出现上述状况就能够作以下措施:
state = {
childLoadSuccess: true
}
// 特定函数,记住就行
static getDerivedStateFromError(error) {
return {
childLoadSuccess: false
}
}
render() {
return (
<div> { this.state.childLoadSuccess ? <B/> : <h4>B组件发生了异常 </h4> } </div>
)
}
复制代码
定义一个状态 childLoadSuccess
用来标志 B 组件是否渲染出错,当 B 组件渲染出错时触发 getDerivedStateFromError
函数,在函数内部返回一个对象将 childLoadSuccess
改掉。这样当 A 组件在渲染时就能够经过 childLoadSuccess
的状态决定渲染 B 组件仍是备用组件或信息。另外还有一个生命周期钩子 componentDidCatch
也可使用,当出现异常时会被触发,能够在这里向服务器提交错误报告。