React基础2(函数式组件钩子,边界错误,懒加载等)

源码地址:https://github.com/BLKNjyty/reactstudy

修改state的两种方法

export default class Demo extends Component {state = {count:0}add = ()=>{//对象式的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,react是异步进行的更新,意思就是修改完状态之后,还不一定更新 *///函数式的setStatethis.setState( state => ({count:state.count+1}))}render() {return (<div><h1>当前求和为:{this.state.count}</h1><button onClick={this.add}>点我+1</button></div>)}
}

(1). setState(stateChange, [callback])------对象式的setState

​ 1.stateChange为状态改变对象(该对象可以体现出状态的更改)

​ 2.callback是可选的回调函数, 它在状态更新完毕、界面也更新后(render调用后)才被调用

(2). setState(updater, [callback])------函数式的setState

​ 1.updater为返回stateChange对象的函数。

​ 2.updater可以接收到state和props。

​ 4.callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。

总结:

​ 1.对象式的setState是函数式的setState的简写方式(语法糖)

​ 2.使用原则:

​ (1).如果新状态不依赖于原状态 ===> 使用对象方式

​ (2).如果新状态依赖于原状态 ===> 使用函数方式

​ (3).如果需要在setState()执行后获取最新的状态数据,

​ 要在第二个callback函数中读取

懒加载

  //1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包const Login = lazy(()=>import('@/pages/Login'))//2.通过指定在加载得到路由打包文件前显示一个自定义loading界面<Suspense fallback={<h1>loading.....</h1>}><Switch><Route path="/xxx" component={Xxxx}/><Redirect to="/login"/></Switch></Suspense>
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>)}
}

函数式组件访问三大属性

事例

function Demo(){//console.log('Demo');//返回数据,第一个是state 第二个是更新state的方法//每次调用都会走一遍,但是react会把count存储下来,再次执行不会右变为0.而是在上次的基础上+1const [count,setCount] = React.useState(0)const myRef = React.useRef()//相当于类式组件的生命周期钩子,//第一个参数相当于两个钩子:DidMount和DidUpdate。DidUpdate是否生效取决于第二个数组参数//[]写个空数组代表谁也不检测, 回调函数只会在第一次render()后执行。不写的话就代表所有的元素都检测//同时第一个参数返回的函数就相当于willUnMount钩子React.useEffect(()=>{let timer = setInterval(()=>{setCount(count => count+1 )},1000)return ()=>{clearInterval(timer)}},[])//加的回调function add(){//setCount(count+1) //第一种写法//第二种写法:setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值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

1. React Hook/Hooks是什么?

(1). Hook是React 16.8.0版本增加的新特性/新语法
(2). 可以让你在函数组件中使用 state 以及其他的 React 特性

2. 三个常用的Hook

(1). State Hook: React.useState()
(2). Effect Hook: React.useEffect()
(3). Ref Hook: React.useRef()

3. State Hook

(1). State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
(2). 语法: const [xxx, setXxx] = React.useState(initValue)  
(3). useState()说明:参数: 第一次初始化指定的值在内部作缓存返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
(4). setXxx()2种写法:setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值

4. Effect Hook

(1). Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
(2). React中的副作用操作:发ajax请求数据获取设置订阅 / 启动定时器手动更改真实DOM
(3). 语法和说明: useEffect(() => { // 在此可以执行任何带副作用操作return () => { // 在组件卸载前执行// 在此做一些收尾工作, 比如清除定时器/取消订阅等}}, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行(4). 可以把 useEffect Hook 看做如下三个函数的组合componentDidMount()componentDidUpdate()componentWillUnmount() 

5. Ref Hook

(1). Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
(2). 语法: const refContainer = useRef()
(3). 作用:保存标签对象,功能与React.createRef()一样

Fragment

作用:可以不需要有真实的Dom根标签

export default class App extends Component {render() {return (<Fragment><Demo/></Fragment>)}
}

Context

理解

一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信

使用

1) 创建Context容器对象:const XxxContext = React.createContext()  2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:<xxxContext.Provider value={数据}>子组件</xxxContext.Provider>3) 后代组件读取数据://第一种方式:仅适用于类组件 static contextType = xxxContext  // 声明接收contextthis.context // 读取context中的value数据//第二种方式: 函数组件与类组件都可以<xxxContext.Consumer>{value => ( // value就是context中的value数据要显示的内容)}</xxxContext.Consumer>

注意

在应用开发中一般不用context, 一般都它的封装react插件

实例

//创建Context对象
const MyContext = React.createContext()
const {Provider,Consumer} = MyContext
export default class A extends Component {state = {username:'tom',age:18}render() {const {username,age} = this.statereturn (<div className="parent"><h3>我是A组件</h3><h4>我的用户名是:{username}</h4>{/* B组件和他的子组件都能收到 */}<Provider value={{username,age}}><B/></Provider></div>)}
}class B extends Component {render() {return (<div className="child"><h3>我是B组件</h3><C/></div>)}
}/* class C extends Component {//声明接收contextstatic contextType = MyContextrender() {const {username,age} = this.contextreturn (

我是C组件

我从A组件接收到的用户名:{username},年龄是{age}

)} } */
// 函数式组件的接收方式(当然类式组件也可以用) function C(){return (<div className="grand"><h3>我是C组件</h3><h4>我从A组件接收到的用户名:{/* 模板字符串,{里面全是js,写字符串包含变量就这么写} */}<Consumer>{value => `${value.username},年龄是${value.age}`}</Consumer></h4></div>) }

组件优化

Component的2个问题

  1. 只要执行setState(),即使不改变状态数据, 组件也会重新render()

  2. 只当前组件重新render(), 就会自动重新render子组件 ==> 效率低

效率高的做法

只有当组件的state或props数据发生改变时才重新render()

原因

Component中的shouldComponentUpdate()总是返回true

解决

办法1: 重写shouldComponentUpdate()方法比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
办法2:  使用PureComponentPureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true注意: 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false  不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化

事例

export default class Parent extends PureComponent {state = {carName:"奔驰c36",stus:['小张','小李','小王']}addStu = ()=>{/* const {stus} = this.statestus.unshift('小刘')this.setState({stus}) */const {stus} = this.statethis.setState({stus:['小刘',...stus]})}changeCar = ()=>{//this.setState({carName:'迈巴赫'})const obj = this.stateobj.carName = '迈巴赫'console.log(obj === this.state);this.setState(obj)}/* shouldComponentUpdate(nextProps,nextState){// console.log(this.props,this.state); //目前的props和state// console.log(nextProps,nextState); //接下要变化的目标props,目标statereturn !this.state.carName === nextState.carName} */render() {console.log('Parent---render');const {carName} = this.statereturn (<div className="parent"><h3>我是Parent组件</h3>{this.state.stus}&nbsp;<span>我的车名字是:{carName}</span><br/><button onClick={this.changeCar}>点我换车</button><button onClick={this.addStu}>添加一个小刘</button><Child carName="奥拓"/></div>)}
}class Child extends PureComponent {/* shouldComponentUpdate(nextProps,nextState){console.log(this.props,this.state); //目前的props和stateconsole.log(nextProps,nextState); //接下要变化的目标props,目标statereturn !this.props.carName === nextProps.carName} */render() {console.log('Child---render');return (<div className="child"><h3>我是Child组件</h3><span>我接到的车是:{this.props.carName}</span></div>)}
}

render属性

如何向组件内部动态传入带内容的结构(标签)?

Vue中: 使用slot技术, 也就是通过组件标签体传入结构  
React中:使用children props: 通过组件标签体传入结构使用render props: 通过组件标签属性传入结构, 一般用render函数属性

children props

xxxx

{this.props.children}
问题: 如果B组件需要A组件内的数据, ==> 做不到 

render props

 }>
A组件: {this.props.render(内部state数据)}
C组件: 读取A组件传入的数据显示 {this.props.data} 

事例

不仅可以传递参数的优点,还可以随时替换子组件,类似于Vue的插槽技术

export default class Parent extends Component {render() {return (<div className="parent"><h3>我是Parent组件</h3>{/* 你好 */}{/* 给A标签的render属性传入一个函数,该函数形参为name,返回为C标签(这个标签名字随便叫啥都行) */}<A render={(name)=><C name={name}/>}/></div>)}
}class A extends Component {state = {name:'tom'}render() {console.log(this.props);const {name} = this.statereturn (<div className="a"><h3>我是A组件</h3>{/* {this.props.children} */}{this.props.render(name)}</div>)}
}class B extends Component {render() {console.log('B--render');return (<div className="b"><h3>我是B组件,{this.props.name}</h3></div>)}
}

边界错误

理解:

错误边界:用来捕获后代组件错误,渲染出备用页面

特点:

只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误

使用方式:

getDerivedStateFromError配合componentDidCatch

// 生命周期函数,一旦后台组件报错,就会触发
static getDerivedStateFromError(error) {console.log(error);// 在render之前触发// 返回新的statereturn {hasError: true,};
}componentDidCatch(error, info) {// 统计页面的错误。发送请求发送到后台去console.log(error, info);
}

事例

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/>}</div>)}
}
export default class Child extends Component {state = {users:[{id:'001',name:'tom',age:18},{id:'002',name:'jack',age:19},{id:'003',name:'peiqi',age:20},]// users:'abc'}render() {return (<div><h2>我是Child组件</h2>{this.state.users.map((userObj)=>{return <h4 key={userObj.id}>{userObj.name}----{userObj.age}</h4>})}</div>)}
}

组件通信方式总结

方式:
	props:(1).children props(2).render props消息订阅-发布:pubs-sub、event等等集中式管理:redux、dva等等conText:生产者-消费者模式
组件间的关系
	父子组件:props兄弟组件(非嵌套组件):消息订阅-发布、集中式管理祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(用的少)

==参考b站尚硅谷视频 链接 ==


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部