React笔记

87 阅读16分钟

React笔记

王银浩1年前 (2022-07-26)react54

 1.数据绑定
 2.事件绑定
 3.生命周期
 4.组件:组件通信
 5.vue-cli
  路由;vue-router
  数据交互:axois
  状态:vuex
  UI:element-ui vant

react

1.创建项目

 npm i create-react-app -g //全局安装脚手架 5.0 node(14+)win7(node12) win10(node卸载了安装新的node)
 cnpm i yarn -g //安装yarn 包管理工具
 
 create-react-app  demo //创建项目 demo项目名称
 
 cd demo
 npm start //localhost:3000

目录结构

 -demo
  -build 打包后生成的文件夹,类似vue的dist
  -node_modules 依赖包
  -public 静态资源
   index.html 唯一的页面
  .gitignore 远程仓库不需要备份文件
  package.json 项目管理文件
  readme.md
  -src 源文件
   index.js 入口文件
   App.js 根组件

index.js

 import React from 'react';
 import ReactDOM from 'react-dom';
 import App from './App';
 ReactDOM.render(
   <App />,
   document.getElementById('root')
 );

App.js

 function App() {
   return (
     <div>
      123
     </div>
   );
 }
 
 export default App;

2.介绍

1.react 是FaceBook开发和维护的;

2.react做的是SPA;

3.react 采用的是JSX语法。

 //JSX -js +xml
 
 <father></father>
 let div='<div></div>'
 
 let div2=<div>
     <span>123</span>
     </div>
     
 function aa(){
     
 }

4.react是单向数据流框架

5.react核心思想:一切皆组件。

3.数据绑定(JSX语法)

1.非表单元素绑定数据 {}

 <div>name:{name}</div>
 <div>price:{price.toFixed(2)}</div>
 <div>{a > 20 ? '大数' : '小数'}</div>

2.属性绑定:{}

<img src={imgUrl} alt="" />

<a href={company.url} title={company.name} aa={company.name}>
    <img src={company.logo} alt="" />
</a>

3. 在react中,对象和数组是不能直接展示到页面的,需要通过JSON.stringify()

<div>company:{JSON.stringify(company)}</div>

4. 条件渲染 {三元判断}  如果不出现任何节点的话用null

{
    arr.length > 0 ? <div>有数据{name}</div> : <div>暂无数据</div>
}
{
    isshow ? <div>弹框</div> : null
}

5. jsx中,遇到了<使用XML解析,遇到了{使用js解析

6.jsx写注释

 {/* 6.jsx写注释 */}

7.列表渲染 {arr.map(item=>return 节点)}

<ul>
    {list.map(item => {
        return <li key={item.id}>{item.name}</li>
    })}
</ul>

8.动态类名

8.1 react 中css使用类名用className代替class
<div className="box"></div>
8.2 动态类名: {三元}
  <div className={isRed ? 'block red' : 'block orange'}></div>

9.动态行间样式 style={json}

  <div style={{ color: color, fontSize: '30px' }}>从前有座山,山里有座庙</div>

4.组件注册

函数组件
import React from 'react'

export default function Fn() {
    return (
        <div>
            
        </div>
    )
}
类组件
import React, { Component } from 'react'

export default class Fn extends Component {
    render() {
        return (
            <div>
                
            </div>
        )
    }
}
函数组件 VS 类组件
类组件有生命周期,有state,通过this.props接收父组件传递的数据;
函数组件没有生命周期,没有state,通过props接收父组件传递的数据.
函数组件是不需要实例化的,他只是纯计算,渲染方面优于类组件。
            函数组件      类组件
生命周期    没有            有
父传子      props接收     this.props接收
state       没有           有

            函数组件是不需要实例化的,他只是纯计算,渲染方面优于类组件。
            实际场景:
                home  类组件[需要生命周期、state]----------------------业务组件
                    banner 函数组件【不需要生命周期、不需要state,数据从父组件传递到props上即可】--木偶组件
                    cate

5.state

// 1.只有state的数据变了,页面才会渲染,在页面中用到的变量都在state中声明
// 2.读取state的数据,建议使用  let { name, age, sex,num } = this.state
// 3.如果state要全部传递给子组件,建议使用{...this.state}
/* 4.修改state state不允许直接修改
    4.1 修改state需要调用setState(),慎用 ++ 
    4.2 修改数组,三步走:123放.如果做添加,还可以使用 ... 
    4.3 修改json,使用 ...
    4.4 setState()会引起render()重新执行,所以在render中不能使用setState(),否则会陷入死循环。
    4.5 this.setState()是异步的,如果想要取到修改后的新值,需要在回调函数中处理
*/

6.事件绑定

1.如何绑定事件?
onClick={()=> this.fn() }  
onClick={this.fn.bind(this)} bind第一个参数是调用该函数的对象,一般传this

eg:

<div className='box'>
    <h3>如何绑定事件?</h3>

    <button onClick={() => this.fn()}>点击触发fn</button>
    <button onClick={this.fn.bind(this)}>bind-点击触发fn</button>
</div>
2.如何传参?
箭头函数:正常传递
bind:  第1个实参传递给了方法的this对象,第2个实参对应第1个形参

eg:

<div className='box'>
    <h3>如何传参?</h3>
    <button onClick={() => this.add(3, 5)}>3,5</button>
    <button onClick={this.add.bind(this, 1020)}>bind-10,20</button>
</div>
3.event对象如何获取?
显示传参: 箭头函数的参数就是event
隐示传参:bind 的最后一个参数就是event,但是不用写

eg:

<div className='box'>
    <h3>event对象如何获取?</h3>

    <button onClick={(e)=>this.getEvent(e)}>箭头函数获取event</button>
    <button onClick={this.getEvent.bind(this)}>bind 获取event</button>

    <button onClick={(e)=>this.getEvent2(10,e)}>箭头函数:10,e</button>
    <button onClick={this.getEvent2.bind(this,20)}>bind: 20 e</button>
</div>
4.如何阻止传播 阻止默认?
阻止默认 e.preventDefault()
阻止传播 e.stopPropagation()

1.props

 // 1.props读取建议使用 let {name,age}=props
 // 2.props 如果要全部传递给子组件,使用...  eg:  <ChildChild {...props}></ChildChild>
 // 3.props不仅可以传递数据,还可以传递方法
 // 4.父传子,父组件通过props将数据传给子组件;子传父,父组件通过props将方法传递给了子组件
 /* 5.对于类定义组件来说,构造函数中是不能直接使用this.props。
     如果你想要在构造函数中使用props,那么需要super(props)
     super() 目的是为了调用父类的构造函数;super(props)是为了在构造函数中使用this.props */
 // 6.组件嵌套的内容在props.children

2.父子组件通信

父传子

父组件通过props将数据传给子组件

父组件

 <ChildFn arr={this.state.arr}></ChildFn>

子组件

  let { arr } = props; //接收使用

子传父

父组件通过props将方法传递给了子组件

父组件

  <ChildFn  del={index=>this.del(index)}></ChildFn>

子组件

  let { del } = props; //接收使用
  <button onClick={()=>del(index)}>删除</button>

3.ref

1.获取DOM节点

 constructor() {
         super()
 
         // 1.1 创建一个ref对象
         this.aa = React.createRef()
     }
 {/* 1.2 将ref对象绑定到节点上 */}
 <div ref={this.aastyle={{ width"100px"height"100px"border"1px solid #ccc" }}></div>
changeBg(color) {
    // 1.3 通过 this.aa.current 获取节点
    this.aa.current.style.background = color;
}

2.父组件获取子组件的实例

constructor() {
        super()

         // 2.1 创建一个ref对象
        this.child=React.createRef()
    }
{/* 2.2 将ref对象绑定到子组件上 */}
<Child ref={this.child}></Child>
changeName(name){
    //修改子组件的值
    // 2.3 通过 this.child.current 获取子组件实例
    this.child.current.changeName(name)
}

4.表单绑定

取值赋值
input type="text"e.target.valuevalue
input type="radio"e.target.valuechecked
input type="checkbox"e.target.valuee.target.checkedchecked
selecte.target.valuevalue
textareae.target.valuevalue
受控组件非受控组件
实时验证
一次性验证
实现禁用状态
特殊格式的输入
一个数据多个输入
input的数据和state的数据挂钩,这样的input叫受控组件;
input的数据和state的数据不挂钩,这样的input叫非受控组件
受控组件 :实时验证      实现禁用状态     特殊格式的输入(身份证号)   一个数据多个输入【推荐】
非受控组件:一次性验证  实现不了禁用状态  做不了特殊格式的输入(身份证号)实现不了一个数据多个输入的

5.生命周期

初始期:
    *constructor:初始化数据
      UNSAFE_componentWillMount:渲染之前 了解即可【已弃用】
    *render:渲染期 根据数据,生成虚拟DOM节点,渲染到页面
    *componentDidMount:组件挂载完成:ajax window|document绑定事件、jquery
更新期:state props
    *shouldComponentUpdate(nextProps,nextState) :判断是否更新
        不写:更新期: componentWillUpdate->render->componentDidUpdate
        写了
            没有return 报错
            return true 更新期: 
              shouldComponentUpdate-->componentWillUpdate->render->componentDidUpdate
            return false 更新期: shouldComponentUpdate
     UNSAFE_componentWillUpdate:更新之前,了解即可【已弃用】
    render:渲染期  
        当调用setState(),react会进入和解过程。根据新的状态生成一棵新的虚拟DOM树,和原本的DOM树做diff算法,对比哪些节点发生了改变。
        计算出最小的更新算法,开始更新。
    componentDidUpdate:更新完成
销毁期
    *componentWillUnmount:销毁之前 清除计时器,清除window|document上的方法

6.组件优化

1.componentWillUnmount

2.父子组件渲染问题

父组件有很多数据,eg:name,num,arr ...
子组件展示父组件的部分数据,eg:num
当父组件num变了,我们希望子组件渲染,如果是父组件name变了,我们希望子组件不要渲染。

父组件传递数据给子组件

{/* 1.shouldComponentUpdate */}

<ChildClass num={num}></ChildClass>

{/* 2.PureComponent */}

<ChildClass2 num={num} json={json} ></ChildClass2>

{/* 3.React.memo() */}

<ChildFn num={num} json={json}></ChildFn>
1.shouldComponentUpdate 类定义组件

子组件通过shouldComponentUpdate  实现优化

shouldComponentUpdate(nextProps,nextState){
    // console.log("nextProps:",nextProps);
    // console.log("props:",this.props);
    if(nextProps.num===this.props.num){
        return false;
    }
    return true;
}
2.PureComponent  类定义组件
import React, { PureComponent } from 'react'
export default class ChildClass2 extends PureComponent {
    render() {
        console.log("子组件22重新渲染了");
        let {num,json}=this.props
        return (
            <div className='box'>
                <h3>类定义子组件</h3>
                <div>num:{num}</div>
                <div>json:{JSON.stringify(json)}</div>
                
            </div>
        )
    }
}

PureComponent 和shouldComponentUpdate如何选择

PureComponent 浅比较,对于简单类型是可以直接检测到改变。但是如果接收数据是引用类型,可能会检测不到数据的改变,所以引用类型父组件那边修改的时候需要用拷贝。
PureComponent :是state和props变了,都计算是否更新
Component state|props变了,都不计算,直接更新

假设一个类定义组件只有props,使用PureComponent * [如果加上仓库,就都是使用PureComponent]
假设一个类定义组件只有state,就不需要计算是否需要更新,直接更新即可,我们使用Component *
假设一个类定义组件state和props,我们使用shouldComponentUpdate+Component
3.React.memo() 函数组件
import React from 'react'

// React.memo()函数组件的优化方案
function ChildFn(props) {
    console.log("函数组件被执行了");
    let {num,json}=props
    return (
        <div className='box'>
            <h3>函数定义子组件</h3>
            <div>num:{num}</div>
            <div>json:{JSON.stringify(json)}</div>
        </div>
    )
}
export default React.memo(ChildFn)
一个函数,参数是组件,返回值是组件。这种函数称为:高阶组件(HOC)。
React.memo是函数,他是一个高阶组件。
高阶组件的作用:增强原来组件的功能

1.组件优化

1.componentWillUnmount

2.父组件传递部分数据给子组件

1.shouldComponentUpdate [类定义组件]
2.PureComponent [类定义组件 只有props]
3.React.memo() [函数定义组件]

3.Fragment

如果一个组件的根节点,不需要,可以使用来代替

写法1:

 export default function Fragment() {
     return (
         <React.Fragment>
             <h3>这是一个组件</h3>
             <p>这是内容</p>
         </React.Fragment>
     )
 }

写法2:

 import React,{Fragmentfrom 'react'
 export default function MyFragment() {
     return (
         <Fragment>
             <h3>这是一个组件</h3>
             <p>这是内容</p>
         </Fragment>
     )
 }

写法3:

 //<Fragment></Fragment> 简写为 <></>
 export default function MyFragment() {
     return (
         <>
             <h3>这是一个组件</h3>
             <p>这是内容</p>
         </>
     )
 }

4.错误边界处理 componentDidCatch

1.封装了一个组件ErrorBoundary.jsx

 import React, { Component } from 'react'
 
 export default class ErrorBoundary extends Component {
     constructor(){
         super()
         this.state={
             haveError:false
         }
     }
     componentDidCatch(){
         this.setState({
             haveError:true
         })
     }
     render() {
         let {haveError}=this.state;
         return (
             <>
                {
                    haveError? <div>此处暂时无法使用</div>:this.props.children
                }
             </>
         )
     }
 }

2.使用

 <ErrorBoundary>
    <List></List>
 </ErrorBoundary>

5.HOC 高阶组件

定义:

一个函数,参数是组件,返回一个新组件。这样的函数叫高阶组件。

目的:

增强组件的功能

语法:
 function log(C){
     return function(){
         console.log("组件被加载了");
   
         return (
             <>
                 <header className='header'></header>
                 <C></C>
             </>
         )
     }
 }
案例:withRequest
 let withRequest = (url) => (C) => {
     return class extends Component {
         constructor() {
             super()
             this.state = {
                 arr: [],
                 isFinishedfalse
             }
         }
         componentDidMount() {
             let that = this;
             $.ajax({
                 url,
                 success(res) {
                     that.setState({
                         arr: res.data,
                         isFinishedtrue
                     })
                 }
             })
         }
         render() {
             let { arr, isFinished } = this.state
             return (
                 <>
                     {
                         isFinished ? <C arr={arr}></C> : <div>正在努力加载中。。。</div>
                     }
                 </>
             )
         }
     }
 }
 export default withRequest

调用:

 let NewBanner=withRequest("/mock/banner.json")(Banner)

路由react-router-dom@5.0.0

1.路由模式 HashRouter BrowserRouter

HashRouter:hash路由  BrowserRouter:history路由

 import {HashRouter,BrowserRouterfrom "react-router-dom"
 
 ReactDOM.render(
   <HashRouter>
     <App />
   </HashRouter>
   ,
   document.getElementById('root')
 );
 

2.路由出口 Switch

 import { Switch } from "react-router-dom";
  <Switch></Switch>

3.路由规则 Route

 import {  Route } from "react-router-dom";
 <Switch>
     {/* 路由规则 */}
     <Route path="/login" component={Login}></Route>
     <Route path="/index" component={Index}></Route>
     <Route path="/list" component={List}></Route>
     <Route path="/detail" component={Detail}></Route>
     <Route path="/search" component={Search}></Route>
     {/* 重定向 */}
     <Redirect to="/login"></Redirect>
 </Switch>

4.重定向 Redirect

 import {   Redirect } from "react-router-dom";
  {/* 重定向 */}
     <Redirect to="/login"></Redirect>

5.路由导航-组件

Link NavLink 没有条件的跳转就可以使用 NavLink比Link多一个activeClassName

 import {   Link,Navlink } from "react-router-dom";
{/* 路由导航*/}
<Link to="/search">Link搜索</Link>
<NavLink to="/search">NavLink搜索</NavLink>
activeClassName
<footer>
    <NavLink activeClassName="select" to="/index/home">首页</NavLink>
    <NavLink activeClassName="select" to="/index/cate">分类</NavLink>
    <NavLink activeClassName="select" to="/index/shop">购物车</NavLink>
    <NavLink activeClassName="select" to="/index/mine">我的</NavLink>
</footer>

6.路由导航-编程式导航

{/* 
                编程式导航:
                    this.props.history.push(path) 添加一条新的历史记录
                    this.props.history.replace(path)用新的历史记录替换当前历史记录
                    this.props.history.go(-1) 返回
                    this.props.history.goBack() 返回
                */}
 <Button type="primary" 
     onClick={()=> this.props.history.push("/search")}>push 搜索</Button>
<Button type="warning" 
    onClick={()=>this.props.history.replace("/search")}>replace 搜索</Button>
注意:

对于非路由组件,是不能直接使用编程式导航的。

1.需要路由组件将props向下传递,非路由组件收到才可以使用history 。

 <GoBack {...this.props}></GoBack>

2.withRouter

import {withRouter} from "react-router-dom"

class GoBack extends React.Component{
    render(){
        return (
            <Icon type="left" onClick={()=>this.props.history.go(-1)} />
        )
    }
}
export default withRouter(GoBack);

7.路由传参

1.search传参(?)

传参

<Link to="/detail?id=1&a=2&c=3"></Link>

接受参数

let search=this.props.location.search ;//'?id=1&a=2&c=3'

解析参数1:

import querystring from "querystring"
let d=querystring.parse(search.slice(1));//{id:"1",a:"2",b:"3"}

解析参数2:

let myURL = new URL("http://localhost:3000" + search);
// myURL.searchParams 是一个Map
let id = myURL.searchParams.get("id");//1
let type = myURL.searchParams.get("a");//2
2.动态路由(:)

传参:

<Link to="/detail/1/2/3"></Link>

修改路由规则

<Route path="/detail/:id/:a/:b" component={Detail}></Route>

取参数

this.props.match.params //{id:"1",a:"2",b:"3"}

8.懒加载

1.通过React.lazy()引入

let Register = React.lazy(() => import("./pages/Register/Register"));
let Index = React.lazy(() => import("./pages/Index/Index"));

2.通过Suspense包裹路由出口

export default function App() {
  return (
    <Suspense fallback={<div>正在努力加载中...</div>}>
      {/* 路由出口 */}
      <Switch>
        {/* 路由规则 */}
        <Route path="/login" component={Login}></Route>
        <Route path="/register" component={Register}></Route>
      
      </Switch>
    </Suspense>
  );
}

9.权限

1.登录拦截

1.登录成功存一个用户信息

2.自己封装了一个组件PrivateRoute

import React, { Component } from 'react'
import {Route,Redirectfrom "react-router-dom"
// 登录拦截
export default class RrivateRoute extends Component {
    render() {
        let info=sessionStorage.getItem("info")
        return (
            <>
               {info ?<Route {...this.props}></Route>:<Redirect to="/login"></Redirect>}
            </>
        )
    }
}

3.需要使用登录拦截的用PrivateRoute来定义路由规则,不需要登录拦截使用Route

<Switch>
    {/* 路由规则 */}
    <Route path="/login" component={Login}></Route>
    <Route path="/register" component={Register}></Route>
    <PrivateRoute path="/index" component={Index}></PrivateRoute>
    <PrivateRoute path="/list" component={List}></PrivateRoute>
    <PrivateRoute path="/detail/:id" component={Detail}></PrivateRoute>
    <PrivateRoute path="/search" component={Search}></PrivateRoute>
    {/* 重定向 */}
    <Redirect to="/login"></Redirect>
</Switch>
2.路由独享守卫
{/* 二级路由出口 */}
<Switch>
    <Route path="/index/home" component={Home}></Route>

    {/* 路由渲染组件,如果直接输出组件,选择component
          如果输出组件有条件,那么,使用render().使用render需要手动给路由组件添加props
           */}
    <Route path="/index/mine" render={(props)=>{
            console.log(props);
            //取出type
            let type=sessionStorage.getItem("type")
            return type==='1'?<Mine {...props}></Mine>:<div>你没有权限</div>
        }}></Route>
</Switch>

UI框架

ant design  [PC]

ant design mobile [app]

mobile.ant.design/zh/componen…

1.安装 【5.0.0】

 npm install --save antd-mobile@next

2.使用

import { Button } from 'antd-mobile'

数据交互

1.配置代理

[package.json]

{
    "proxy":"http://localhost:3000",
}

2.搭建数据交互

1.安装

cnpm i axios --save
cnpm i qs --save

注意:重启前端项目

2.封装 【http.js】

和vue一样

3.所有的api 写在了【api.js】

和vue一样

redux

1.三大原则

 1.单一数据源
 2.state是只读的
 3.只能通过纯函数修改state

2.redux生态

react(视图层)---》react-redux《---- redux(状态层)---》redux-thunk《---  后端(数据)

 使用场景:
 1.非父子组件共享数据;
 2.从长期效益考虑,希望更好的组件之外管理状态。

3.redux

1.安装

 npm i redux --save

2.创建仓库【store/index.js】

 import {
     createStore
 } from "redux";
 
 
 let initState={
     name:"妲己",
     age:20
 }
 
 
 //reducer 纯函数
 /**
  * state: 上一次修改后导出的结果。对于第一次来说,没有上一次,所以让他的默认值为initState
  * action:任务|动作
  *          {type:"changeName",name:'王昭君'}
  *          {type:"changeAge",age:'100'}
 */
 function reducer(state=initState,action){
     if(action.type==='changeName'){
         state.name=action.name
     }
     if(action.type==='changeAge'){
         state.age=action.age
     }
     return state
 }
 
 let store=createStore(reducer)
 
 //导出仓库
 export default store;

3.仓库方法

store.getState()
 // store.getState() 读取仓库的数据
 console.log(store.getState()); //{name: '妲己', age: 20}
store.dispatch(action)
 //store.dispatch(action) 派发任务
 store.dispatch({type:"changeName",name:'王昭君'})
store.subscribe(callback)
 // 添加订阅者  
 /*
 添加订阅者之后,一旦有新数据变化了,就需要通知订阅者。 通知的过程叫:发布订阅。
 */
 var un=store.subscribe(()=>{
     console.log("数据变了");
 })
取消订阅者
 // 取消订阅者
 un()

4.action creator

//action creator [actions]
export let actions = {
    changeName(name) => {
        return {
            type: types.CHANGE_NAME,
            name
        }
    },
    changeAge(age) => ({type: types.CHANGE_AGE,age})
}
store.dispatch(actions.changeName('貂蝉'))
store.dispatch(actions.changeAge(200))

5.action types

// action types 
const types = {
    CHANGE_NAME: "CHANGE_NAME",
    CHANGE_AGE: "CHANGE_AGE"
}

4.react-redux

为了方便组件关联状态,可以借助模块react-redux.

1.安装

npm i react-redux --save

2.Provider

react-redux为了方便后期组件可以拿到状态层的state,提供了Provider组件。

// 入口文件
//引入仓库
import store from "./store"
//引入Provider组件
import { Provider } from "react-redux"


ReactDOM.render(
    <Provider store={store}>
        <App />
    </Provider>
    ,
document.getElementById("root"))

3.容器型组件

3.1connect

React-Redux 提供connect方法,用于从 UI 组件生成容器组件。connect的意思,就是将这两种组件连起来.

import React, { Component } from 'react'
import { connect } from "react-redux"
class C extends Component {
    render() {
        console.log(this.props);
        let { name, age, changeAge, changeName } = this.props
        return (
            <div className='box'>
             
            </div>
        )
    }
}
let mapStateToProps=(state) => {
    return {
       
    }
}
let mapDispatchToProps=(dispatch) => {
    return {
        
    }
}

// connect是一个函数,返回值是一个高阶组件
export default connect(mapStateToProps,mapDispatchToProps)(C)

3.2 mapStateToProps

mapStateToProps是一个函数。它的作用就是像它的名字那样,建立一个从(外部的)state对象到(UI 组件的)props对象的映射关系。

作为函数,mapStateToProps执行后应该返回一个对象,里面的每一个键值对就是一个映射。

let mapStateToProps=(state) => {
    return {
        name: state.name,
        age: state.age
    }
}

3.3mapDispatchToProps

mapDispatchToPropsconnect函数的第二个参数,用来建立 UI 组件的参数到store.dispatch方法的映射。也就是说,它定义了哪些用户的操作应该当作 Action,传给 Store。它可以是一个函数,也可以是一个对象。

如果mapDispatchToProps是一个函数,会得到dispatch

let mapDispatchToProps=(dispatch) => {
    return {
        changeName(name) => {
            dispatch(actions.changeName(name))
        },
        changeAgeage => dispatch(actions.changeAge(age))
    }
}

4.容器型组件VS展示型组件

容器型组件展示型组件
关注点逻辑[取数据、更新状态]UI的展现
对redux是否感知
读数据从redux的store中获取从props中获取
写数据发送redux actions调用props的回调
如何创建通过react-redux connect创建手写
是否是路由组件
是否做逻辑是业务组件是木偶组件
一般如何定义类定义组件函数定义组件

5.selectors

为了方便容器型组件从状态层取数据,可以借助selectors。

//导出数据 selectors [getters]
export let getName=(state)=>state.name
export let getAge=state=>state.age

组件使用:

let mapStateToProps = (state) => {
    return {
        // 1.取数据可以借助selector函数
        name: getName(state),
        age: getAge(state)
    }
}

6.bindActionCreators

bindActionCreators是redux的一个自带函数,作用是将单个或多个ActionCreator转化为dispatch(action)的函数集合形式。

开发者不用再手动dispatch(actionCreator(type)),而是可以直接调用方法。

可以实现简化书写,减轻开发的负担。

【仓库中的actions】

//action creator [actions]
export let actions = {
    changeName(name) => {
        return {
            type: types.CHANGE_NAME,
            name
        }
    },
    changeAge(age) => ({type: types.CHANGE_AGE,age})
}

【组件引入使用】

import { bindActionCreators } from "redux"
let mapDispatchToProps = dispatch => {
    return {
        // 2.取方法借助bindActionCreators() 直接一次将actions上的方法全部导入props上
        aa:bindActionCreators(actions,dispatch)
    }
}

使用

let { aa:{changeName,changeAge} } = this.props;

5.reducer拆分

1.在store下创建modules文件夹,里面新建了2个模块 【home.js】和【shop.js】

【home.js】

//state
const initState = {
    banner: [],
    list: ["首页的列表"]
}

// actionTypes 
// 为了防止各个模块的types互相影响,建议每个type前面加上模块名
const types = {
    HOME_CHANGE_BANNER: "HOME_CHANGE_BANNER",
    HOME_CHANGE_LIST: "HOME_CHANGE_LIST"
}

//reducer
const reducer = (state = initState, action) => {
    switch (action.type) {
        case types.HOME_CHANGE_BANNER:
            //action={type:"changeBanner",banner:[1,2,3]}
            return {
                ...state,
                banner: action.banner
            }
            break
        case types.HOME_CHANGE_LIST:
            //action={type:"changeList",list:[1,2,3]}
            return {
                ...state,
                list: action.list
            }
            break
        default:
            return state;
    }
}

//actionCreactor
export const actions = {
    changeBanner: (banner) => ({
        type: types.HOME_CHANGE_BANNER,
        banner
    }),
    changeList: list => ({
        type: types.HOME_CHANGE_LIST,
        list
    })
}

//导出数据 selectors  
//注意:selectors函数中的state是从组件传递过来的,组件传递的state是整个仓库的state,不是当前模块的state.
export const getBanner = state => state.home.banner
export const getList = state => state.home.list

//导出reducer 
export default reducer

【shop.js】

//state
const initState = {
    list: ["购物车的列表"]
}

// actionTypes 
const types = {
    SHOP_CHANGE_LIST: "SHOP_CHANGE_LIST"
}

//reducer
const reducer = (state = initState, action) => {
    switch (action.type) {
        case types.SHOP_CHANGE_LIST:
            //action={type:"changeList",list:[1,2,3]}
            return {
                ...state,
                list: action.list
            }
            break
        default:
            return state;
    }
}

//actionCreactor
export const actions = {

    changeList: list => ({
        type: types.SHOP_CHANGE_LIST,
        list
    })
}

//导出数据 selectors 
export const getList = state => state.shop.list


//导出reducer 
export default reducer

2.在仓库的入口文件 【store/index.js】合并reducer

import {
    createStore,
    combineReducers
} from "redux"
import HomeReducer from "./modules/home"
import ShopReducer from "./modules/shop"

let reducer = combineReducers({
    // key:value  key:模块名,value:该模块的reducer 
    homeHomeReducer,
    shopShopReducer
})


let store = createStore(reducer)

3.注意: selectors中的state是仓库的state .

//注意:selectors函数中的state是从组件传递过来的,组件传递的state是整个仓库的state,不是当前模块的state.
export const getBanner = state => state.home.banner
export const getList = state => state.home.list

4.actionTypes不能重复

// 为了防止各个模块的types互相影响,建议每个type前面加上模块名
const types = {
    HOME_CHANGE_BANNER: "HOME_CHANGE_BANNER",
    HOME_CHANGE_LIST: "HOME_CHANGE_LIST"
}

6.reselect

在视图中有很多数据是经过现有的状态计算得到的。在selectors中,只要仓库的state变了,所有的selector函数都会重新执行。有些数据希望依赖的数据变了,再重新计算。那么就需要使用reselect .

1.安装

npm i reselect --save

2.引入 【store/shop.js】

import {createSelector} from "reselect"
//导出数据 selectors 
export const getList = state => state.shop.list
export const getName=state=>10;
export const getAllPrice=createSelector([getList,getName],(list,name)=>{
    console.log("开始计算!!!!!");
    let sum = 0;
    list.map(item => {
        sum += item.price * item.num
    })
    return sum;
})
语法:
export const getAllPrice=createSelector([fn1,fn2,...],(fn1返回值,fn2返回值,...)=>{
    //逻辑
   
})

7.中间件

组件--dispatch(action) -中间1(action) next-->-中间2(action)->中间3(action)-....->reducer(action)

语法:

function middleware(store){
    return function(next){
        return function(action){
            //逻辑 next(action)
        }
    }
}

let middleware= store => next=>action=>{
         //逻辑 next(action)
 }

案例1:

import {
    createStore,
    applyMiddleware,
    combineReducers
} from "redux"

let myLogger = store => next => action => {
    console.group("进到我的中间件了")
    console.log("本次触发的action:",action);
    console.log("当前的仓库数据:",store.getState())
    next(action)
    console.log("修改后的仓库数据:",store.getState())
    console.groupEnd()
}

let store = createStore(reducer,applyMiddleware(myLogger))

案例2:redux-logger

安装

cnpm i redux-logger --save

引入

import logger from 'redux-logger'

let store = createStore(reducer,applyMiddleware(logger))

案例3: redux-thunk

8.redux-devTools

1.安装扩展程序

码云搜索:redux-devtools

2. 安装依赖
npm install --save redux-devtools-extension

引入:【store/index.js】

import { composeWithDevTools } from 'redux-devtools-extension';
const store = createStore(reducer, composeWithDevTools( applyMiddleware(logger)));

过滤器

1.【filter/index.js】

export let filterPrice = price => price.toFixed(2)
export let filterTel = tel => tel.slice(0, 3) + "****" + tel.slice(7)

2.组件使用 引入

import { filterPrice } from '../filters'
<div>总价:{filterPrice(allPrice)}</div>

计算属性

在render中实现重复执行的计算操作。

  render() {
        let allPrice=this.state.list.reduce((val,item)=>val+item.price*item.num,0)
        return (
            <div className='box'>
                <h3>计算属性</h3>
                <div>总价:{allPrice}</div>
                </div>
        )
    }