React快速暴力入门(四)
一.了解与使用 react-redux
前面也说了,react-redux 其实就是 redux的升级版,对许多地方进行了优化,但在学习他之前,需要我们进行一些对 redux的优化知识。  1. 使用容器组件和UI组件 其目的就是为了把组件身上太多的活进行拆分,分为UI组件(内组件)和容器组件(外组件),两个组件之间使用 props进行通信,对 store那边的请求状态,更改状态的活交给容器组件来干,而通过状态来编写页面,更新渲染等活,就交给 UI组件来干。 了解了这个后,就可以开始使用 react-redux了 2. 安装 react-redux
npm i react-redux -S
这个就不多说了 3. 创建文件夹 对于容器组件,我们都是使用 containers文件夹进行存储。
- containers ------ 用于存储容器组件的文件夹
- redux ------ 用于存储 react-redux相关的文件夹
4. 创建容器组件 容器组件通过 react-redux的 connect() 方法进行创建。
// 引入 Count的 UI组件
import Count from "../../components/Count";
// 引入 connect用于连接 UI组件与 redux
import { connect } from 'react-redux'
// 该函数的返回值作为状态传递给 UI组件
function mapStateToProps(state){
return {
count: state,
}
}
// 该函数的返回值作为操作状态的方法传递给 UI组件
function mapDispatchToProps(dispatch){
return {
add: data=>{
console.log(1234);
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Count);
使用 connect( )( )创建并暴露一个 Count组件的容器组件。 调用 connect时有两个参数,且必须时函数。
- mapStateToProps( state ): 该函数的返回值会作为 状态 传递给 UI组件
state: 参数 state 为 react-redux 默认操作好的 store.getState() - mapDispatchToProps( dispatch ): 该函数的返回值会作为 操作状态的方法 传递给 UI组件,有语法糖写法,传入个对象即可(看下面代码)。
dispatch: 参数 dispatch 为 react-redux 默认给的 store.dispatch 方法
这里只是为了讲解方便,使用时可以使用语法糖的。 注意的几个地方:
- 一般都是把 UI组件 与 容器组件 写在一个文件中,至于存在哪个文件夹中看公司需求(一般都是 containers)
- 容器组件里的 store不是通过引入使用,而是作为 props传递给容器组件的标签的。
- 语法糖写法:
// 引入 Count的 UI组件
import Count from "../../components/Count";
// 引入 connect用于连接 UI组件与 redux
import { connect } from 'react-redux'
// 精简写法
export default connect(
state => ({count: state}),
{
add: data=> console.log(1234, data)
}
)(Count);
5. Provider 组件 如果你有许多个容器组件,那么每个容器组件都要传入 store,那么是不是觉得太繁琐了呢?所以 react-redux 提供了 Provider组件用于处理这个问题,只需要在根标签处使用并把 store传递进去,他就可以自动判断哪些组件需要使用 store并自动传递给它。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import store from "./redux/store"
// 优化3: 使用自带的 Provider自动判断哪些组件需要使用 store,从而自动导入
import { Provider } from "react-redux";
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
6. 监听状态变化 有没有发现,我上面的代码已经没有再写 store.subscribe() 监听状态变化了,是因为我们创建容器组件的 connect已经帮我们进行监听了。 7. 容器组件之间通信 其实这个很简单,还记的 connect的第一个函数的第一个参数吗?传递给 UI组件的是 props,他的参数是 state,这个 state是 store.getState()。 可是此时你的 store.getState() 不再是一个单纯的值,而是所有 reducer的对象,所以我们可以在里面获取到其他容器组件的值
export default connect(
state => ({count: state.count, personLength: state.persons.length}),
{
increment: createIncrementAction,
decrement: createDecrementAction,
asyncIncrement: createIncrementAsyncAction,
}
)(Count);
8. 文件规范  9. 再说一遍,能不用就别用这东西!!
二.React16.8 与一些扩展
1. lazy() 与 Suspense 之前的组件,都是一并加载的,这样会给服务器带来较大的负担,所以 react推出了 lazy()懒加载,进行组件的按需加载。 与之一起出来的是 Suspense,他解决的是组件在加载过程中还没加载出来时的白屏,用于展示其他的内容。
import React, { Component, lazy, Suspense } from 'react'
import {Route, Link} from "react-router-dom"
// import Home from "./Home";
// import About from "./About";
import Loading from "./Loading";
// lazy() 方法的参数是一个函数,返回需要加载的组件
const Home = lazy(()=>import("./Home"))
const About = lazy(()=>import("./About"))
export default class index extends Component {
render() {
return (
<div>
<Link to="/home">Home</Link>
<Link to="/about">About</Link>
<hr/>
{/* Suspense 用于解决加载组件时的白屏,可以显示其他的内容,而其他内容不允许使用 lazy加载 */}
<Suspense fallback={<Loading/>}>
<Route path="/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
</Suspense>
</div>
)
}
}
- lazy 的参数是一个函数,使用 import 导入一个组件并返回
- Suspense 的属性 fallback属性的属性值是 组件标签 而不是组件
- Suspense所使用的组件不能使用 lazy进行懒加载。
2. Hook React16.8可以说是给函数式组件一次春天,因为他有了Hook,可以实现一些 state、生命周期函数,refs等特性。 让我们一个个来看: (1) stateHook 可以让函数式组件实现使用 state的特性:
export default function Demo() {
// useState返回一个数组,只有两个元素(只有两个元素)
// 元素1 为状态,元素2 为更新状态的方法
// 第一次调用时以及将count进行底层存储,所以 Demo重复渲染不会重置count数据
const [count, setCount] = React.useState(0); // 初始值赋为 0
const [name, setName] = React.useState("Tom");
function add(){
// 进行状态赋值
// setCount(count + 1); // 写法1,直接将原来的状态值覆盖
setCount(count=> count+1); // 写法2,参数为函数,接受原本的状态值,返回新的状态值,覆盖原来的状态
}
return (
<div>
<h3>名字:{name}</h3>
<h2>当前求和:{count}</h2>
<button onClick={add}>点击加一</button>
</div>
)
}
(2) EffectHook 可以让函数式组件实现类似生命周期钩子的特性:
export default function Demo() {
const [count, setCount] = React.useState(0);
const [name, setName] = React.useState("Tom");
function add(){
setCount(count=> count+1);
}
function updName(){
setName(name=>"Jerry");
}
function unmount(){
ReactDOM.unmountComponentAtNode(document.querySelector("#root"));
}
// useEffect接收两个参数,第一个为函数体,第二个为检测的对象(数组),当检测的对象状态发生改变,就会触发函数
// 不填写第二参数时,检测所有元素,相当于 componentDidUpdate生命周期函数
React.useEffect(()=>{
// console.log("asdf")
let timer = setInterval(()=>{
setCount(count=>count+1);
},1000);
return ()=>{ // 在 useEffect中的函数体里返回的函数,相当于 componentWillUnmount生命周期函数
console.log("unmount")
clearInterval(timer);
; }
},[]) // 数组为空是谁也不检测,只执行一次函数,相当于生命周期函数的 componentDidMount
return (
<div>
<h2>当前求和:{count}</h2>
<button onClick={add}>点击加一</button>
<h2>当前名字:{name}</h2>
<button onClick={updName}>修改名字为Jerry</button>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
可以把 useEffect Hook 看作三个函数的结合:
- componentDidMount
- componentDidUpdate
- componentWillUnmount
(3) refHook refHook 可以让函数式组件实现类似 ref的特性
export default function Demo() {
const [count, setCount] = React.useState(0);
function add(){
setCount(count=> count+1);
}
function show(){
// 获取文本框内容
alert(myInput.current.value);
}
// 生成一个容器
const myInput = React.useRef();
return (
<div>
<h2>当前求和:{count}</h2>
{/* 绑定容器 */}
<input type="text" ref={myInput}/>
<button onClick={add}>点击加一</button>
<button onClick={show}>点击展示数据</button>
</div>
)
}
这个就没啥难度。 3. Fragment 在 react渲染组件的时候,当你的组件越来越多时,你有没有发现你的 DOM层级越来越多,有些不美观,所以就出现了 Fragment。 可以使用 Fragment标签代替组件的根标签,在 React解析的时候会被处理掉, 从而让生成出来的代码的层级更加简洁。
export default class Demo extends Component {
render() {
return (
// 使用空标签可以达到一样的效果,但是空标签不允许包含任何的属性
<Fragment key={1}>
<input type="text"/>
<input type="text"/>
</Fragment>
)
}
}
4. Context Context是一种新的组件通信方式,常用于【祖组件】和【后代组件】之间通信。
// 1. 创建一个 Context容器对象
const UserNameContext = React.createContext();
// 1.1 拿到 Provider与 Consumer属性
const {Provider, Consumer} = UserNameContext;
export default class A extends Component {
state={username: "Tom"}
render() {
return (
<div className="a">
<h1>我是A组件</h1>
<p>我的用户名是:{this.state.username}</p>
{/* 2 使用组件,后代组件都能收到来自 value的值,就在 this上的 context属性上(需提前声明) */}
<Provider value={this.state.username}>
<B/>
</Provider>
</div>
)
}
}
class B extends Component {
// 3. 声明接受 Context
static contextType = UserNameContext; // 此方法只适用于 类组件
render() {
console.log(this.context); // Tom
return (
<div className="b">
<h2>我是B组件</h2>
<p>A的用户名是:{this.context}</p>
<C/>
</div>
)
}
}
function C() {
return (
<div>
<div className="c">
<h3>我是C组件</h3>
{/* 3.2 使用 Consumer组件进行声明接受(类组件和函数式组件都可以) */}
<Consumer>
{value=> ("A的用户名是:" + value)}
</Consumer>
</div>
</div>
)
}
5. PureComponent 在组件中,只要执行了 setState(),即使没更新状态数据,组件也会重新 render()。 只要组件重新 render(),就会自动重新 render()子组件,纵使子组件没有使用到父组件任何数据。 这两种情况都会导致重复渲染,使得效率低下。 效率高的做法:只有组件的state或props发生变化时菜重新render()。
- 解决方法1:通过生命周期函数 shouldComponentUpdate() 进行数据判断在进行重新渲染
- 解决方法2:类组件通过继承 PureComponent组件,自动进行数据判断(浅对比—判断地址值)(常用)
import React, { Component, PureComponent } from 'react'
export default class Parent extends PureComponent {
state = {carName: "奔驰"}
changeCar = ()=>{
this.setState(state=>({carName:"迈巴赫"}));
}
// shouldComponentUpdate有两个参数,分别是准备修改的 props和 state
// shouldComponentUp date(nextProps, nextState){
// console.log(nextProps, nextState); // 目标要修改的props和state
// console.log(this.props, this.state); // 还未修改原本的props和state
// return !this.state.carName === nextState.carName;
// }
render() {
console.log("parent render");
return (
<div className="parent">
<h1>Parent</h1>
<p>我的车是:{this.state.carName}</p>
<button onClick={this.changeCar}>点击换车</button>
<Child/>
</div>
)
}
}
class Child extends PureComponent {
// shouldComponentUpdate(nextProps){
// return !this.props.carName === nextProps.carName
// }
render() {
console.log("child render");
return (
<div className="child">
<h2>Child</h2>
{/* <p>父亲的车是:{this.props.carName}</p> */}
</div>
)
}
}
6. 父子组件 不多说,直接上代码:
import React, { Component, PureComponent } from 'react'
import "./index.css"
export default class Parent extends PureComponent {
state = {carName: "奔驰"}
changeCar = ()=>{
this.setState(state=>({carName:"迈巴赫"}));
}
render() {
console.log("parent render");
return (
<div className="parent">
<h1>Parent</h1>
<p>我的车是:{this.state.carName}</p>
{/* A组件 与 B组件 形成父子组件的第二种方法 */}
{/* <A>
<B/>
</A> */}
{/* 类似于 Vue的插槽 */}
<A render={(name)=><B name={name}/>}/>
</div>
)
}
}
class A extends PureComponent {
render() {
console.log("A render");
return (
<div className="a">
<h2>A</h2>
{/* A组件 与 B组件 形成父子组件的第一种方式 */}
{/* <B/> */}
{/* {this.props.children} */}
{this.props.render("Tom")}
</div>
)
}
}
class B extends PureComponent {
render() {
console.log("B render");
return (
<div className="b">
<h2>B</h2>
<p>{this.props.name}</p>
</div>
)
}
}
7. ErrorBoundary 当你的组件存在父子组件关系时,如果说你的子组件出现了错误,那么会导致父组件一并崩掉,那有没有什么办法,可以把错误控制在一个组件里,不让他扩散呢? 答案是有的,有两个函数:
- getDerivedStateFromError(error)
- componentDidCatch(error, info)
上代码:
import React, { Component, Fragment } from 'react'
import Child from "./Child";
// 错误边界即把组件的错误信息控制在一个组件中,不使他扩散而导致程序崩溃
export default class Person extends Component {
state = {
hasError: "", // 用于标识子组件是否产生错误
}
// 当子组件发生错误时会触发该生命周期函数,且参数为错误信息
// 只适用于生产环境,只能捕获后代组件生命周期产生的错误
static getDerivedStateFromError(error){
// 一般用于处理错误出现时返回给用户展示的东西
console.log("出错了");
console.log(error);
}
// 组件渲染过程中出错就会触发该生命周期函数
componentDidCatch(error, info){
// 一般用于统计错误,反馈给雾浮起,用于通知程序员进行bug修改
console.log("渲染组件出错");
console.log(error, info)
}
render() {
return (
<Fragment>
<h2>我是Parent组件</h2>
{
this.state.hasError ?
<h2>当前网络不大行,建议买高级网络套餐好吧</h2> :
<Child/>
}
</Fragment>
)
}
}
8. 组件通信方式总结
- 组件之间的关系:
- 几种通信方式:
- props
- children props
- render props
- 消息订阅
- pubsub, event
- 集中式管理
- redux, dva, react-redux
- conText生产者, 消费者模式
- 较好的搭配方式:
- 父子组件:props
- 兄弟组件: 消息订阅发布,集中式管理
- 祖孙组件:消息订阅发布,集中式管理,conText
三.总结
总结了四期 总算完结了 这些就是我学习React的一些学习笔记了,日后如果还有其他的内容的话应该会深究后写成单独的文章了。
|