持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第7天,点击查看活动详情
本文适用于react15+
React是一个只专注于界面渲染的前端框架,他只做一件事,就是更好的渲染数据。数据流的处理和状态的维护需要开发者来控制,使用相对比较灵活。
1. Hello React:第三方库引入版
经过如下的操作,可以实现在一个单HTML文件中食用React,完全离线的前提是你要下载好相应的js文件
<!-- 准备好一个“容器” -->
<div id="test"></div>
<!-- 库下载到本地 (完全离线开发) -->
<!-- 引入react核心库 -->
<!-- <script type="text/javascript" src="../js/react.development.js"></script> -->
<!-- 引入react-dom,用于支持react操作DOM -->
<!-- <script type="text/javascript" src="../js/react-dom.development.js"></script> -->
<!-- 引入babel,用于将jsx转为js -->
<!-- <script type="text/javascript" src="../js/babel.min.js"></script> -->
<!-- 不想下载到本地的话,当然也可使用cdn -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script crossorigin="anonymous" integrity="sha384-Fw04PJPC6ayYz0V6lPpRaiQxouxskHt/AjlelA2NWboTMnn5niXpXMyN4hftijud" src="https://lib.baomitu.com/babel-core/5.8.38/browser.min.js"></script>
<script type="text/babel" > /* 此处一定要写babel */
//1.创建虚拟DOM
const VDOM = () => <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
//2.渲染虚拟DOM到页面(React17及以下)
// ReactDOM.render(<VDOM />, document.getElementById('test'))
//3.React18写法(使用上边的CDN即可):
ReactDOM.createRoot(document.getElementById('test')).render(
<React.StrictMode>
<VDOM />
</React.StrictMode>
)
// 当然也可以完全不用组建的思想,这样声明:
// const VDOM = <h1>Hello,React</h1>
// ReactDOM.render(VDOM, document.getElementById('test'))
</script>
CDN地址可在React和Babel官网获取
直接在浏览器打开这个HTML:
2. 试着丰富自己的VDOM
我们可以再改改上面的const VDOM,使自己的html更复杂一些
const VDOM = () => ( /* 此处一定不要写引号,因为不是字符串 */
<h1 id="title">
<span>Hello,React</span>
</h1>
)
当然了,创建VDOM也可以不使用jsx,react提供了相应的API
//1.创建虚拟DOM
const VDOM = () => React.createElement('h1', {id:'title'}, React.createElement('span',{},'Hello,React'))
PS. 使用react自带的api相对繁琐,不利于节点嵌套书写,没有jsx直观
关于虚拟DOM:
1. 本质是Object类型的对象(一般对象)
2. 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM上那么多的属性。
3. 虚拟DOM最终会被React转化为真实DOM,呈现在页面上。
3. 上手jsx
jsx语法规则:
- 定义虚拟DOM时,不要写引号。
- 标签中混入JS表达式时要用
{}。 - 样式的类名指定不要用class,要用
className。 - 内联样式,要用
style={{ key: value }}的形式去写。 - 只有一个根标签
- 标签必须闭合
- 标签首字母规范
(1). 若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错。 (2). 若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。 - 循环表达式中,每一项应加入唯一标识的
key值
下面给一写规范定义的例子:
// 一定注意区分:【js语句(代码)】与【js表达式】
// 1.表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
// 下面这些都是表达式:
// (1). a
// (2). a+b
// (3). demo(1)
// (4). arr.map()
// (5). function test () {}
// 2.语句(代码):
// 下面这些都是语句(代码):
// (1).if(){}
// (2).for(){}
// (3).switch(){case:xxxx}
//模拟一些数据
const data = ['Angular','React','Vue']
//1.创建虚拟DOM
const VDOM = () => (
<div>
<h1>前端js框架列表</h1>
<ul>
{
data.map((item,index)=>{
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
//2.渲染虚拟DOM到页面 (以React17为例)
ReactDOM.render(<VDOM />, document.getElementById('test'))
在上边的例子中,使用一个{}的代码块包裹一个循环的js表达式,来达到循环渲染的效果。
4. 组件的类别
函数式
//1. 创建函数式组件
// 写法1
function MyComponent() {
console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
// 写法2
const MyComponent = () => {
return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
}
//2.渲染组件到页面(以React17为例)
ReactDOM.render(<MyComponent/>, document.getElementById('test'))
执行了ReactDOM.render(.......之后,发生了什么?
- React解析组件标签,找到了 MyComponent 组件。
- 发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
分析:
- 优点:使用纯函数,更轻量,无需创建类实例,开销小。便于组件分装和高阶组件使用。推荐使用,hooks全支持!
- 缺点:没有this,没有状态(有了hooks后可以解决)。
类式
//2. 创建类式组件
class MyComponent extends React.Component {
render(){
//render是放在哪里的?—— MyComponent的原型对象上,供实例使用。调用次数1+N
//render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
console.log('render中的this:',this);
return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
}
}
//2.渲染组件到页面(React17为例)
ReactDOM.render(<MyComponent/>,document.getElementById('test'))
render调用次数为1+N,指的是初始加载时render一次,当每次setState时,便更新一次render函数。函数式组件中其实也是这样,每一次状态更新都会触发整个函数的重渲染,函数式组件可以使用useMemo、useCallback等钩子来做优化。
与函数式对比:
- 类式虽然可以用this,但是类组件中的this是可以改变的,某些情况下增大了空指针的概率。
- 类式组件体量更加臃肿,不容易拆分。
- 函数式组件更灵活,优化方法和可扩展性更强。
- 新版本功能不在倾向于支持类式组件了。
5. 组件实例三大属性 state、props、ref
state组件状态,React.Component自带属性
// state类式使用,函数式使用见hooks
//初始化状态
state = {isHot:false,wind:'微风'}
render(){
const {isHot,wind} = this.state
return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
}
//自定义方法————要用赋值语句的形式+箭头函数
changeWeather = ()=>{
const isHot = this.state.isHot
this.setState({isHot:!isHot})
}
hooks使用:
const [isHot, setIsHot] = useState(false);
props父子传参
// 1.props类式使用
//创建组件
class Person extends React.Component{
render(){
const {name,age,sex} = this.props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
//渲染组件到页面
ReactDOM.render(<Person name="jerry" age={19} sex="男"/>,document.getElementById('test1'))
ReactDOM.render(<Person name="tom" age={18} sex="女"/>,document.getElementById('test2'))
const p = {name:'老刘',age:18,sex:'女'}
// console.log('@',...p);
// ReactDOM.render(<Person name={p.name} age={p.age} sex={p.sex}/>,document.getElementById('test3'))
ReactDOM.render(<Person {...p}/>,document.getElementById('test3'))
// 2.参数类型限制
//对标签属性进行类型、必要性的限制
// npm使用时,可安装 prop-types 包
<script type="text/javascript" src="../js/prop-types.js"></script>
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string,//限制sex为字符串
age:PropTypes.number,//限制age为数值
speak:PropTypes.func,//限制speak为函数
}
//指定默认标签属性值
Person.defaultProps = {
sex:'男',//sex默认值为男
age:18 //age默认值为18
}
// 3. 函数式组件使用
//创建组件
function Person (props){
const {name,age,sex} = props
return (
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age}</li>
</ul>
)
}
父组件也可以给子组件传递一个回调事件,在子组件中调用该事件便可实现子向父传递。
refs
// ref类式使用,函数式使用见hooks
//创建组件
class Demo extends React.Component{
//展示左侧输入框的数据
showData = (e) => {
const {input1} = this.refs
alert(input1.value)
}
//展示右侧输入框的数据
showData2 = (e) => {
const {input2} = this.refs
alert(input2.value)
alert(this.myRef.current.value);
}
// 使用API
myRef = React.createRef()
render(){
return(
<div>
<input ref="input1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点我提示左侧的数据</button>
<input onBlur={this.showData2} ref={c => this.input2 = c } type="text" placeholder="失去焦点提示数据"/>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>
</div>
)
}
}
hooks使用:
const Foo = React.forwardRef((props, myRef) => {
const [value, setValue] = useState(0);
useImperativeHandle(ref, () => ({
// 暴露数据
getValue: () => value
}));
return (
<input type="text" value={value} onChange={e => setValue(e.target.value)}/>
);
});
// 使用
const fooRef = useRef();
// 查看ref中暴露出来的值
console.log(fooRef.current.getValue())
...
<Foo ref={fooRef} />
6. 处理表单
- 非受控表单
//创建组件
class Login extends React.Component {
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username,password} = this
alert(`你输入的用户名是:${username.value},你输入的密码是:${password.value}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input ref={c => this.username = c} type="text" name="username"/>
密码:<input ref={c => this.password = c} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
- 受控表单
//创建组件
class Login extends React.Component {
//初始化状态
state = {
username:'', //用户名
password:'' //密码
}
//保存用户名到状态中
saveUsername = (event) => {
this.setState({username:event.target.value})
}
//保存密码到状态中
savePassword = (event) => {
this.setState({password:event.target.value})
}
//表单提交的回调
handleSubmit = (event) => {
event.preventDefault() //阻止表单提交
const {username,password} = this.state
alert(`你输入的用户名是:${username},你输入的密码是:${password}`)
}
render(){
return(
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" name="username"/>
密码:<input onChange={this.savePassword} type="password" name="password"/>
<button>登录</button>
</form>
)
}
}
处理表单中,类式组件和函数式并无新的区别
7. 高阶函数使用
// 渲染一个自定义表单
// 一级组件
function FormItem ({ field }) {
const filterItem = () => ({
Input: <input field={field} type="input" {...props} />,
InputPassword: (
<input field={field} type="password" {...props} />
),
TextArea: <textarea field={field} {...props} />,
Select: <select field={field}>{
field.options?.map(op => <option key={op.value} value={op.value}>{op.label}</option>)
}</select>,
Date: <input field={field} type="date" {...props} />,
Content: field.content,
});
return <div clasName="form-item">
{filterItem[field.type]}
</div>
}
// 二级组件
function Form({ items }) {
return <>
{items.map((item, index) => <FromItem key={index} field={item}>)}
</>
}
// 三级使用
function App() {
const items = [
{ type: 'Input', onChange: () => {} },
{ type: 'Select', options: [{ value: 1, label: 'apple' }, { value: 2, label: '包娜娜' }] }
];
return <Form items={items} />
}
8. 新旧生命周期
- v16之前版本
简介:
componentWillReceiveProps在props变化之前触发,hooks中可使用useEffect代替shouldComponentUpdate用于判断是否需要更新组件,在初始化加载或者setState时触发。是一种性能优化钩子,hooks中,可使用useMemo、useCallback等代替。componentWillUpdate在组件即将变化前触发,尽量避免使用,在这个钩子中若修改了状态值,会造成渲染死循环。hooks中可使用useEffect代替。componentDidUpdate在组件更新之后调用,尽量避免使用,在这个钩子中若修改了状态值,会造成渲染死循环。componentWillMount在组件加载之前调用,在v17中已经移除。若v16中出现警告,可以使用UNSAFE_componentWillMount代替。componentDidMount会在组件挂载后(插入 DOM 树中)立即触发。依赖于 DOM 节点的初始化操作应该放在这里。如需通过网络请求获取数据,也可以写在这里。hooks中可使用useEffect代替componentWillUnmount在组件销毁之前触发,hooks中可使用useEffect代替
- v16+
对比于 React 15 废弃了 componentWillMount ,新增了 getDerivedStateFromProps
getDerivedStateFromProps 的设计初衷是替换 componentWillReceiveProps ,它有且仅有一个作用:让组件在 props 变化时派生/更新 state
9. 开始使用hooks
hooks,翻译过来还是钩子,字面意思跟React生命周期钩子是一个意思,至少是为了完成同样的任务才造出来的。所以就可以这样理解,hooks就是给函数式组件量身定做的,用于取代类式组件生命周期钩子的一类组件API。
与类式组件对比
参照组:
class Demo extends React.Component {
// 1
state = {count:0}
// 2
myRef = React.createRef()
add = () => {
this.setState(state => ({count:state.count+1}))
}
unmount = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
show = () => {
alert(this.myRef.current.value)
}
// 3
componentDidMount() {
this.timer = setInterval(() => {
this.setState( state => ({count:state.count+1}))
}, 1000)
}
// 4
componentWillUnmount() {
clearInterval(this.timer)
}
render() {
return (
<div>
<input type="text" ref={this.myRef}/>
<h2>当前求和为{this.state.count}</h2>
<button onClick={this.add}>点我+1</button>
<button onClick={this.unmount}>卸载组件</button>
<button onClick={this.show}>点击提示数据</button>
</div>
)
}
}
上才艺:
function Demo(){
// 1.state
const [count,setCount] = React.useState(0)
// 2.ref
const myRef = React.useRef()
// 3
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1 )
},1000)
return () => {
// 4. unmount
clearInterval(timer)
}
},[])
//加的回调
function add(){
// setCount(count + 1) //第一种写法
setCount(count => count + 1 )
}
//提示输入的回调
function show() {
alert(myRef.current.value)
}
//卸载组件的回调
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
return (
<div>
<input type="text" ref={myRef}/>
<h2>当前求和为:{count}</h2>
<button onClick={add}>点我+1</button>
<button onClick={unmount}>卸载组件</button>
<button onClick={show}>点我提示数据</button>
</div>
)
}
export default Demo
hooks详细
React Hooks就是加强版的函数组件,可以完全不使用 class,就能写出一个全功能的组件,没有了继承,没有了渲染逻辑,没有了生命周期。
userState状态钩子
他将一个普通变量暂存在react里,使得组件更新时刷新函数后,这个值仍能保持原来的状态。
// 计数器
const AddCount = () => {
const [ count, setCount ] = useState(0)
const addcount = () => {
let newCount = count
setCount(newCount+=1)
}
return (
<>
<p>{count}</p>
<button onClick={addcount}>count++</button>
</>
)
}
useContext共享状态钩子
共享全局状态,下面的 AppContext 可以作为单例单独配置。需要使用生产者消费者模式,往context里provide数据。
const Ceshi = () => {
// 可提出来作为单独模块
const AppContext = React.createContext({})
const A = () => {
const { name } = useContext(AppContext)
return (
<p>我是A组件的名字{name}<span>我是A的子组件{name}</span></p>
)
}
const B =() => {
const { name } = useContext(AppContext)
return (
<p>我是B组件的名字{name}</p>
)
}
return (
<AppContext.Provider value={{name: 'hook测试'}}>
<A/>
<B/>
</AppContext.Provider>
)
}
useReducerAction钩子
和redux一样,需要通过页面组件发起action来调用reducer方法,从而改变状态,达到改变页面UI的这样一个过程。所以我们会先写一个Reducer函数,然后通过useReducer()返回给我们的state和dispatch来驱动这个数据流。
轻量级的redux
const AddCount = () => {
const reducer = (state, action) => {
if (action.type === 'add') {
return {
...state,
count: state.count + 1,
}
} else {
return state
}
}
const addcount = () => {
dispatch({
type: 'add'
})
}
const [state, dispatch] = useReducer(reducer, {count: 0})
return (
<>
<p>{state.count}</p>
<button onClick={addcount}>count++</button>
</>
)
}
useEffect副作用钩子
使用最广泛的hooks
// 实现componentDidMount
const AsyncPage = () => {
const [loading, setLoading] = useState(true)
useEffect(() => {
setTimeout(()=> {
setLoading(false)
},5000)
return () => {
// 实现componentWillUnmount
}
})
return (
loading ? <p>Loading...</p>: <p>异步请求完成</p>
)
}
// 实现依赖项update检测
const AsyncPage = ({name}) => {
const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})
// 可检测状态、props变化
useEffect(() => {
setLoading(true)
setTimeout(() => {
setLoading(false)
setPerson({name})
}, 2000)
}, [name])
return (
<>
{loading ? <p>Loading...</p> : <p>{person.name}</p>}
</>
)
}
useMemo、useCallback
在class的时代,我们一般是通过pureComponent来对数据进行一次浅比较,引入Hook特性后,我们可以使用Memo进行性能提升
在一个state变化后,默认react会重新执行整个render函数,使用memo可以提高性能
const Child1 = React.memo((props) => {
console.log("执行子组件1了");
return <div>子组件1上的n:{props.value}</div>;
});
const Child2 = React.memo((props) => {
console.log("执行子组件2了");
return <div>子组件2上的m:{props.value}</div>;
});
// 父组件
return (
<>
<div>
最外层盒子
<Child1 value={n} />
<Child2 value={m} />
<button
onClick={() => {
setN(n + 1);
}}
>
n+1
</button>
<button
onClick={() => {
setM(m + 1);
}}
>
m+1
</button>
</div>
</>
);
// 这样在点击了父组件的按钮后,只会触发局部子组件的刷新
但是将修改state的函数交给子组件后就会失灵, 由于复杂数据类型的地址可能发生改变,于是传递给子组件的props也会发生变化. 这时候就需要 useMemo
<Child2 value={m} onClick={addM} /> //addM是修改M的函数
useMemo使用,与useEffect类似:
const addM = useMemo(() => {
return () => {
setM({ m: m.m + 1 });
};
}, [m]); //表示监控m变化
// 语法糖形式
const addM = useCallback(() => {
setM({ m: m.m + 1 });
}, [m]);
// 最终父组件写作
<>
<div>
最外层盒子
<Child1 value={n} click={addN} />
<Child2 value={m} click={addM} />
<button onClick={addN}>n+1</button>
<button onClick={addM}>m+1</button>
</div>
</>
上面的例子中,使用了useCallback来和useMemo对比。其实就是不同的语法糖。useCallback接受一个回调函数并缓存,useMemo接受回调函数并且执行以后再缓存。
基于上面的特性,useMemo可以实现vue中的计算属性的功能:
// num1 num2通过props获取
const sum = useMemo(() => num1 + num2, [num1, num2]);
...
// 动态显示两个变量的和
<div>{sum}</div>
10. 扩展阅读
setState
setState是react的异步操作,每次调用setState都会触发更新,异步操作是为了提高性能,将多个状态合并一起更新,减少re-render调用。
先做一个测试
state = {count: 0}
//对象式的setState
//1.获取原来的count值
const {count} = this.state
//2.更新状态
this.setState({count:count+1},() => {
console.log(this.state.count);
})
console.log('输出',this.state.count); //0
// React会将多个setState的调用合并成一个来执行,这意味着当调用setState时,state并不会立即更新, 可以使用一个回调来拿上一个更新的值
this.setState( preState => ({count:preState.count+1}))
由此初步推断setState实现了如下两步,合并state,和一次性render:
setState( stateChange ) {
Object.assign( this.state, stateChange );//合并接收到的state
renderComponent( this );//调用render渲染组件(应为异步)
}
由于渲染dom是异步的,故而推测有一个渲染队列:
//创建一个队列
const queue = [];
/**
* 该方法用于保存传过来的一个个state和其对应要更新的组件,但是并不更新,而是先放入该队列中等待操作
*/
const renderQueue = [];
function enqueueSetState( stateChange, component ) {
queue.push( {
stateChange,
component
} );
// 如果renderQueue里没有当前组件,则添加到队列中
if ( !renderQueue.some( item => item === component ) ) {
renderQueue.push( component );
}
}
进而改写setState方法:
setState( stateChange ) {
enqueueSetState( stateChange, this );
}
既然有了队列,肯定要有出队操作:
function flush() {
let item;
// 遍历
while( item = setStateQueue.shift() ) {
const { stateChange, component } = item;
// 如果没有prevState,则将当前的state作为初始的prevState
if ( !component.prevState ) {
component.prevState = Object.assign( {}, component.state );
}
// 如果stateChange是一个方法,也就是setState的第二种形式
if ( typeof stateChange === 'function' ) {
Object.assign( component.state, stateChange( component.prevState, component.props ) );
} else {
// 如果stateChange是一个对象,则直接合并到setState中
Object.assign( component.state, stateChange );
}
component.prevState = component.state;
}
}
如此就实现了使用队列更新state。下面说说dom渲染时机。渲染组件不能在遍历队列时进行,因为同一个组件可能会多次添加到队列中,我们需要另一个队列保存所有组件,不同之处是,这个队列内不会有重复的组件。
// 这也是为什么会有这个声明
const renderQueue = [];
在flush中末尾加入渲染方法即可:
// 渲染每一个组件, 至于renderComponent怎么实现,牵扯到fiber的原理部分了,应该不是入门时该考虑的
while( component = renderQueue.shift() ) {
renderComponent( component );
}
还有一点没有说明,那就是flush的时机,到底积攒多少state才触发一次更改呢:
// 可以利用js事件队列,让setState在同步任务后执行,enqueueSetState函数顶部加入,在同步的push动作完成后才会执行defer
if ( queue.length === 0 ) {
defer( flush );
}
function defer( fn ) {
return Promise.resolve().then( fn );
}
别的延迟方法
function defer( fn ) {
// 16毫秒一次
return requestAnimationFrame( fn );
}
最新的React中,内部机制能检测到的地方, setState就是异步的;在React检测不到的地方,例如setInterval, setTimeout里,setState就是同步触发更新的。
路由懒加载应用
<div className="panel-body">
<Suspense fallback={<Loading/>}>
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Suspense>
</div>
Fragment铺平dom层级
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
<Fragment> 可简写为 <>
PureComponent
PureComponent自带通过props和state的浅对比来实现 shouldComponentUpate(),而Component没有。所以继承了PureComponent后不需要开发者自己实现shouldComponentUpdate,就可以进行简单的判断来提升性能。
renderProps
指一种在 React 组件之间使用一个值为函数的 prop 共享代码的简单技术
class Parent extends React.Component {
render() {
return (
<div className="parent">
<h3>我是Parent组件</h3>
<A render={(name) => <C name={name}/>}/>
</div>
)
}
}
const C = ({ name }) => <span>{name}</span>
class A extends React.Component {
constructor(props) {
super();
this.state = {name:'tom'}
}
render() {
const {name} = this.state
return (
<div className="a">
<h3>我是A组件</h3>
{/* 重点 */}
{this.props.render(name)}
</div>
)
}
}
ReactDOM.createRoot(document.getElementById('test')).render(
<Parent />
)
渲染结果:
getDerivedStateFromError接受error
export default class Parent extends Component {
constructor(props) {
super();
this.hasError = '' //用于标识子组件是否产生错误
}
//当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息
static getDerivedStateFromError(error){
return {hasError: error}
}
// 当有错误发生时, 可以友好地展示 fallback 组件;可以捕捉到它的子元素(包括嵌套子元素)抛出的异常;可以复用错误组件;
componentDidCatch(){
console.log('此处统计render()错误,反馈给服务器,用于通知编码人员进行bug的解决');
}
render() {
return (
<div>
<h2>我是Parent组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>}
</div>
)
}
}