[React] Hook - extension & optimization
第一部分
-
setState
import React, { Component } from 'react' export default class Demo extends Component { state = {count:0} add = ()=>{ // [1] 对象式的setState /* //1.获取原来的count值 const {count} = this.state
//2.更新状态 this.setState({count:count+1},()=>{ console.log(this.state.count); }) //console.log('12行的输出',this.state.count); //0 因为是异步 update state value,所以这不是正确的方法 */ // [2] 函数式的setState this.setState( state => ({count:state.count+1})) // 只需要一步,一行。 } render() { return ( <div> <h1>当前求和为:{this.state.count}</h1> <button onClick={this.add}>点我+1</button> </div> ) } }
import React, { Component,lazy,Suspense} from 'react' import {NavLink,Route} from 'react-router-dom' // import Home from './Home' // import About from './About' import Loading from './Loading' const Home = lazy( ()=> import('./Home') ) const About = lazy( ()=> import('./About') ) export default class Demo extends Component { render() { return ( <div> <div className="row"> <div className="col-xs-offset-2 col-xs-8"> <div className="page-header"><h2>React Router Demo</h2></div> </div> </div> <div className="row"> <div className="col-xs-2 col-xs-offset-2"> <div className="list-group"> {/* 在React中靠路由链接实现切换组件--编写路由链接 */} <NavLink className="list-group-item" to="/about">About</NavLink> <NavLink className="list-group-item" to="/home">Home</NavLink> </div> </div> <div className="col-xs-6"> <div className="panel"> <div className="panel-body"> <Suspense fallback={<Loading/>}> {/* 注册路由 */} <Route path="/about" component={About}/> <Route path="/home" component={Home}/> </Suspense> </div> </div> </div> </div> </div> ) } }
第二部分
-
stateHook (useState)
//类式组件
class Demo extends React.Component { state = {count:0} myRef = React.createRef() add = ()=>{ this.setState(state => ({count:state.count+1})) } unmount = ()=>{ ReactDOM.unmountComponentAtNode(document.getElementById('root')) // 这是被用户触发;该函数“又”触发” componentWillUnmount() } show = ()=>{ alert(this.myRef.current.value) } componentDidMount(){ this.timer = setInterval(()=>{ this.setState( state => ({count:state.count+1})) },1000) } 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> ) } }
如下,一个 useEffect 外加 return,可以替代如上这些功能:组件的后台程序,以及自动卸载。
-- componentDidMount()
-- componentDidUpdate()
-- componentWillUnmount()
function Demo(){ //console.log('Demo'); const [count,setCount] = React.useState(0) const myRef = React.useRef() React.useEffect(()=>{
let timer = setInterval(()=>{ setCount(count => count+1 ) },1000)
return ()=>{ clearInterval(timer) // 相当于 componentWillUnmount() }
},[]) 。。。
-
refHook (useRef)
class Demo extends React.Component { state = {count:0} myInputRef = React.createRef() show = ()=>{ alert(this.myInputRef.current.value) } render() { return ( <div> <input type="text" ref={this.myInputRef}/> <h2>当前求和为{this.state.count}</h2> <button onClick={this.add}>点我+1</button> <button onClick={this.unmount}>卸载组件</button> <button onClick={this.show}>点击提示数据</button> </div> ) } }
function Demo(){ //console.log('Demo'); const myInputRef = React.useRef()//提示输入的回调 function show(){ alert(myInputRef.current.value) } }
第三部分
-
Fragment
是个虚的</div>。
import React, { Component,Fragment } from 'react' export default class Demo extends Component { render() { return ( <Fragment key={1}> <input type="text"/> <input type="text"/> </Fragment> ) } }
-
Context
使用函数组件时,不通过props传参,而是通过更方便的方式。
类组件的弊端,在于provide 会导致每个子类都要写一行“声明代码”。
如下可见,(1) (2) (3) 被添加后,就可以通过(4) 直接从 "爷爷组件" 得到其参数给 "孙子组件"。
//创建Context对象 const MyContext = React.createContext() // (1) const {Provider,Consumer} = MyContext // (2)
export default class A extends Component { state = {username:'tom',age:18} render() { const {username,age} = this.state return ( <div className="parent"> <h3>我是A组件</h3> <h4>我的用户名是:{username}</h4> <Provider value={{username,age}}> <B/> </Provider> </div> ) } }
class C extends Component { //声明接收context static contextType = MyContext // (3) render() { const {username,age} = this.context // (4) return ( <div className="grand"> <h3>我是C组件</h3> <h4>我从A组件接收到的用户名:{username},年龄是{age}</h4> </div> ) } }
如果是“函数孙组件”,则可以考虑 Consumer。当然,也可以考虑Hook的方式,goto: [React] Hook,支持了useContext。
function C(){ return ( <div className="grand"> <h3>我是C组件</h3> <h4>我从A组件接收到的用户名: <Consumer> {value => `${value.username},年龄是${value.age}`} // 这里替代了(4)的方式,可以直接从value这个key中提取参数值。 </Consumer> </h4> </div> ) }
组件优化 PureComponent
Ref: 123 尚硅谷 react教程 扩展8 PureComponent
分析
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
即使是 setState({}) 都会触发render,这不合理,需要优化。
-
效率高的做法
只有当组件的state或props数据发生改变时才重新render()
-
原因
Component中的 shouldComponentUpdate() 总是返回true
解决
办法1: 重写 shouldComponentUpdate()方法 比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
shouldComponentUpdate(nextProps,nextState){ // console.log(this.props,this.state); //目前的props和state // console.log(nextProps,nextState); //接下要变化的目标props,目标state return !this.state.carName === nextState.carName }
办法2: (推荐方法) 使用PureComponent PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
import React, { PureComponent } from 'react' export default class Parent extends PureComponent { } class Child extends PureComponent { }
注意: 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false 不要直接修改state数据, 而是要产生新数据。
红框:返回的是新对象。杜绝蓝框的写法。
addStu = ()=>{ /* const {stus} = this.state stus.unshift('小刘') this.setState({stus}) */ const {stus} = this.state this.setState({stus:['小刘',...stus]}) }
高级 部分
-
renderProps 插槽组件
像常规标签这么去用,写法如下,比较怪异。这是 ChildrenProps.
這招實在太秀了!~~
但,A组件中的数据 如何传递给 B组件?
<A> <B/> </A>
解决方案就是 RenderProps 如下:类似Vue 的插槽技术。
定义A
class A extends Component { state = {name:'tom'} render() { console.log(this.props); const {name} = this.state
return ( <div className="a"> <h3>我是A组件</h3> {this.props.render(name)} </div> ) } }
使用时,可以选择A的子组件。A相当于一个“插槽组件”。
使用A
export default class Parent extends Component { render() { return ( <div className="parent"> <h3>我是Parent组件</h3> <A render={(name)=><C name={name}/>}/> </div> ) } }
-
ErrorBoundary
如果子组件容易 发生exception,父组件就要准备一些处理代码,把错误限制在一个可控的范围。
[React] 09 - Tutorial: components,有讲生命周期。
import React, { Component } from 'react' import Child from './Child' export default class Parent extends Component { state = { hasError:'' //用于标识子组件是否产生错误 } //当Parent的子组件出现报错时候,会触发getDerivedStateFromError调用,并携带错误信息 static getDerivedStateFromError(error){ console.log('@@@',error); return {hasError:error} } componentDidCatch(){ console.log('此处统计错误,反馈给服务器,用于通知编码人员进行bug的解决'); } render() { return ( <div> <h2>我是Parent组件</h2> {this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child/>} # 返回一个error handling web page </div> ) } }