合肥千峰前端培训---React知识点梳理

基础知识整理开源仓库 :)欢迎star

VSCode React插件

ES7 React/Redux/GraphQL/React-Native snippets

react特点

  • 灵活,功能都要自己写,如(动态class用三目)
  • Fiber 算法 将diff算法优化
  • 有虚拟DOM
  • jsx语法糖
  • 单向数据流
  • 组件系统(万物皆为组件)

react脚手架工具

  • 安装全局脚手架工具
    npm i -g create-react-app
    
  • 创建项目
    create-react-app 项目文件夹名
    

jsx

  • React 使用 JSX 来替代常规的 JavaScript。
  • JSX 是一个看起来很像 XML 的 JavaScript 语法扩展。
    const element = 

    Hello, world!

    ;
    这种看起来可能有些奇怪的标签语法既不是字符串也不是 HTML。
    它被称为 JSX, 一种 JavaScript 的语法扩展。 我们推荐在 React 中使用 JSX 来描述用户界面。
  • 这个语法糖需要react依赖
    import React from "react"
    
  • 原理
    JSX-使用React构造组件=>babel编译=>js对象=>reactDom.render()=>dom元素=>插入到页面
    {/* 

    我是标题哦

    一个有味道的标签

    */
    } class Title extends Component { render () { return ( React.createElement('div',{className:'title'}, [React.createElement('h1',null,'我是标题哦'), React.createElement('p',{id:'app'},'一个有味道的标签')] ) ) } }

定义组件

  1. 函数式组件
    import React from 'react'
    function App () {
    // ()是为了return后面写的代码可换行
      return (
        <h1>你好,React</h1>
      )
    }
    export default App
    
  2. 类组件
    import React,{Component} from 'react'
    class Content extends Component{
      render () {
        return (
          <h2>我是内容组件</h2>
        )
      }
    }
    export default Content
    

使用组件(注意只能有一个根组件)

import React from "react"
import reactDom from "react-dom"
// 导入组件
import App from "./App"
import Content from './Content'
// 类组件引入直接使用,React会自动进行实例化,调用实例的方法
// var content = new Content()
// content.render()

reactDom.render(
  // 这样直接调用,但写法太low了
  // App(),
  // 使用jsx语法糖,组件名首字母必须大写
  <App/>,
  document.querySelector('#root')
)

React中写样式/类名

  1. 外联样式
    import './myStyle.css'
    // 直接导入css文件,记住就算是当前路径下也要加'./'
    // 他没有像vue的scoped局部作用域,它是全局的,所以使用时最好给每个组件的根元素设置独一无二的id或类名
    class Style1 extends Component {
      render () {
        return (
          // react提供`className`属性表示类名,因为class是js关键字
          <div className="box">
            我是div盒子
          </div>
        )
      }
    }
    
  2. 内联样式
  • style="xxx"直接写会报错,当多个属性是要有;,编译成js对象会报错
  • 应该使用{{}},外层{}提供js环境,内层是个对象,用于表示样式
    import React,{Component} from 'react'
    import './Style.css'
    import ClassNames from 'classnames'
    import {Btn} from './Btn'
    // 01. 动态class/style
    // 02. 使用classnames库
    // 03. 使用styled-components(React中万物皆为组件)
    export default class Style2 extends Component {
      render () {
        return (
          <div style={ {fontSize: "20px", background: 'pink', color: 'skyblue', width: '200px' ,height: '200px',lineHeight: '200px',textAlign:'center'} }>
            我是一个div
            <p style={ 1<2?{color: 'green'}:{color: 'red'} }>
              我是哪个?
            </p>
    
            <p className={1>2?'green':'red'}>
              我吃
            </p>
    
            <p className={ClassNames('box',{red: 1<2})}>
              使用classnames库
            </p>
            {/* 使用styled-components */}
            <Btn>我是Btn组件</Btn>
          </div>
        )
      }
    }
    
    Btn.js
    styled-components写法有点怪
    import styled from 'styled-components'
    var Btn = styled.button`
    background: #F60;
    border-radius: 5px;
    width: 100px;
    line-height: 30px;
    color: skyblue;
    outline: none;
    `
    
    export {
      Btn
    }
    

props

  1. 组件传参
  • 函数式组件
    通过自定义属性传参

    <App a="我是参数"/>
    

    或直接函数实参传参

    App({a:"我是参数"})
    

    通过函数内置参数接受

    var App = (props) =>{
      return (
        // 

    你好!{props.a}

    // 组件嵌套

    父向子通信

    ) }
  • 类组件
    自定义属性传参

    <Test a="我是参数"/>
    

    通过this.props.xxx接收

    // 构建类组件
    class Test extends Component {
      render(){
        // React里会自己实例化,this指向那个实例
        return (
          

    我是一个类组件{this.props.a}

    ) } }
  1. 单向数据流
  • React是单向数据流,数据主要从父节点传递到子节点(通过props)。容器组件向傻瓜组件传参。状态提升:将子组件中的数据都提升到公共的祖先组件中管理。
  • 如果顶层(父级)的某个props改变了,React会重渲染所有的子节点。
  • 不可以使用this.props直接修改props,因为props是只读的,props是用于整个组件树中传递数据和配置。
  1. 定义默认属性
    跟js里面类定义静态属性的写法一样
    import React, { Component } from 'react'
    
    export default class DefaultProp extends Component {
      // static defaultProps = {
      //   title: "我是默认值"
      // }
      render() {
        return (
          <div className="prop">
            {this.props.title}
          </div>
        )
      }
    }
    
    DefaultProp.defaultProps = {
      title: '我也是默认值'
    }
    
  2. props验证
    安装依赖prop-types
    npm i prop-types
    
    使用
    import React,{Component} from 'react'
    import PropTypes from 'prop-types'
    
    export default class PropCheck extends Component {
      static defaultProps = {
        title: '我是默认的标题'
      }
      render () {
        return (
          <div className="checkProp">
            {this.props.title}
          </div>
        )
      }
    }
    
    PropCheck.propTypes = {
      // title: PropTypes.string,
      // title: PropTypes.string,
      // b: PropTypes.string.isRequired,
      // obj: PropTypes.object,
      // arr: PropTypes.array,
      // boo: PropTypes.bool,
      fun: PropTypes.func,
    
    };
    

state

  • 组件的局部状态,挂载数据
  • 定义state有两种方法
    export default class Title extends Component {
      // 1.
      // state = {
      //   isLog: false
      // }
      constructor () {
        super()
        // 必须要继承一下父类的属性
        // 2.
        this.state = {
          isLog: false
        }
      }
      render() {
        return (
          <p>state数据</p>
        )
      }
    }
    
  • 更改state
    如果直接更改state里面的数据,数据会改,但组件中使用的数据 值不会变化
    // 直接修改
    this.state.isLog = true;
    console.log(this.state.isLog)
    
    setState设置state
    this.setState({a:1},()=>{console.log('异步更改state完成了')})
    this.setState((prevState,props)=>{a:1},()=>{console.log('异步更改state完成了')})
    
  • 其他
    状态提升、受控组件和非受控组件、有状态组件与无状态组件

数据渲染

  1. 列表渲染
    constructor () {
      super()
      this.state = {
        arr: [1,2,3,4,5]
      }
    }
    render() {
      return (
        <div>
          {/* 列表渲染 */}
          { 
            this.state.arr.map((el,index)=>{
              return (
                <li key={index}>{el}</li>
              )
            })
            //相当于
            // [
            //   
  2. 1
  3. ,
    //
  4. 2
  5. ,
    //
  6. 3
  7. // ] } </div> ) }
  8. 条件渲染
    constructor () {
      super()
      this.state = {
        isChecked: false,
        button: 0,
      }
    }
    render() {
      return (
        <div>
          {/* 条件渲染 */}
          {
            this.state.isChecked? '已选中': '未选中'
          }
          {
            this.state.button?
            <button>按钮二</button>:
            <button>按钮一</button>
          }
        </div>
      )
    }
    
  9. 富文本渲染
    constructor () {
      super()
      this.state = {
        richStr: '

    我是富文本

    '
    } } render() { return ( <div> {/* 富文本渲染 */} { <Fragment> <div dangerouslySetInnerHTML={{__html:this.state.richStr}}> </div> </Fragment> } </div> ) }

组件间通信

  1. props传参

  2. 组件嵌套(类似于vue中的插槽)
    传参

    <Fa a="父向子通信1">
      <h1>父向子通信</h1>
    </Fa>
    

    接收
    this.props.xxx
    this.props.children

    class Fa extends Component {
      render () {
        return (
          <div>我是父组件
            <div>
              {this.props.children}
            </div>
            <p>{this.props.a}</p>
          </div>
        )
      }
    }
    
  3. 在父组件中定义用于改变自身state的方法,通过props传递给子组件,因为复杂数据类型是地址引用,还是父组件自己改变数据的

  4. 自定义事件
    注意:在 componentDidMount中注册事件,在componentWillUnmount中取消事件注册。
    摘自https://www.cnblogs.com/yaoyinglong/p/7806721.html

    var EventEmitter = require('events').EventEmitter;
    import React,{Component} from 'react';
    import ReactDOM from 'react-dom';
    
    let emitter = new EventEmitter();
    
    class ListItem extends Component{
        static defaultProps = {
            checked: false
        };
        constructor(props){
            super(props);
        }
        render(){
            return (
                <li>
                    <input type="checkbox" checked={this.props.checked} onChange={this.props.onChange} />
                    <span>{this.props.value}</span>
                </li>
            );
        }
    }
    
    class List extends Component{
        constructor(props){
            super(props);
    
            this.state = {
                list: this.props.list.map(entry=>({
                    text:entry.text,
                    checked:entry.checked || false
                }))
            };
            console.log(this.state);
        }
    
        onItemChange(entry){
            const {list} = this.state;
            this.setState({
                list:list.map(prevEntry=>({
                    text: prevEntry.text,
                    checked:prevEntry.text === entry.text? !prevEntry.checked : prevEntry.checked
                }))
            });
        //这里触发事件
            emitter.emit('ItemChange',entry);
        }
        render(){
            return (
                <div>
                    <ul>
                        {this.state.list.map((entry,index)=>{
                            return (<ListItem
                                key={`list-${index}`}
                                value = {entry.text}
                                checked = {entry.checked}
                                onChange = {this.onItemChange.bind(this, entry)}
                            />);
                        })}
                    </ul>
                </div>
            );
        }
    }
    
    class App extends Component{
        constructor(props){
            super(props);
        }
        componentDidMount(){
            this.itemChange = emitter.addListener('ItemChange',(msg,data)=>console.log(msg));//注册事件
        }
        componentWillUnmount(){
            emitter.removeListener(this.itemChange);//取消事件
        }
        render(){
            return (
                <List list={[{text:1},{text:2}]}/>
            )
        }
    }
    
    ReactDOM.render(
        <App/>,
        document.getElementById('root')
    );
    
  5. redux

事件

  • react里面的事件是合成事件,遵循驼峰命名法
  • 事件函数使用有三种方法:
  1. 直接onClick = {() => {}}不推荐,因为一般视图上最好不要写太多业务逻辑,不利于代码的后期维护
    import React, { Component } from 'react'
    export default class MyEvent extends Component {
      constructor () {
        super()
        this.state = {
          isLiked : false
        }
      }
    
      render() {
        return (
          <div className="my_event">
            <button onClick= {()=>{this.setState({isLiked: !this.state.isLiked})}}>切换</button>
            <p>
              {this.state.isLiked?'喜欢你':' 讨厌你'}
            </p>
          </div>
        )
      }
    }
    
  2. 以属性的形式定义事件函数,箭头函数获取上下文的this
    import React, { Component } from 'react'
    export default class MyEvent extends Component {
      constructor () {
        super()
        this.state = {
          isLiked : false
        }
      }
    
      render() {
        return (
          <div className="my_event">
            <button onClick={this.clickHandler}>切换</button>
            <p>
              {this.state.isLiked?'喜欢你':' 讨厌你'}
            </p>
          </div>
        )
      }
    
      clickHandler = () => {
        this.setState({isLiked: !this.state.isLiked})
      }
    }
    
  3. 以方法的形式定义事件函数,通过bind改变this的指向;定义属性来接受bind生成的新函数,使用这个属性
    import React, { Component } from 'react'
    export default class MyEvent extends Component {
      constructor () {
        super()
        this.state = {
          isLiked : false
        }
        this.clickHandler = this.clickHandler.bind(this)
      }
    
      render() {
        return (
          <div className="my_event">
            <button onClick={this.clickHandler}>切换</button>
            <p>
              {this.state.isLiked?'喜欢你':' 讨厌你'}
            </p>
          </div>
        )
      }
    
      clickHandler () {
        // 一般来说事件函数里的this是指向事件源的,但框架里面的事件都是重新封装的,里面通常想拿到的是实例
        // react里面直接拿它就给个undefined,你需要手动通过bind的来改变this的指向
        // 但bind每次执行会生成新的函数副本,通常我们定义个属性来保存这个函数,使用那个属性就行了
        // console.log(this)
        this.setState({isLiked: !this.state.isLiked})
      }
    }
    
  • 事件对象
    事件函数的内置参数
    import React, { Component } from 'react'
    import './eventObj.css'
    
    export default class EventObj extends Component {
      constructor () {
        super()
        this.state = {
          arr : [1,2,3,4,5]
        }
      }
      render() {
        return (
          <div>
            {this.state.arr.map((el,i)=>{
              return (
                // 
                //   key={i}
                //   onClick={this.clickHandler}
                //   >
                //   {el}
                // 
  • <li key={i} onClick={(e) => {this.clickHandler1(e,i)}} > {el} </li> ) })} </div> ) } // 事件函数的第一个参数是事件对象 clickHandler = (e) => { // console.log(e) // 获取事件源 var target = e.target //给点击的元素加个类名(背景颜色样式) target.classList.add('greenBg') } // 事件函数传参 // 注意传参时,用函数包一下,不然他会直接调用,包它的函数才是事件函数 clickHandler1 = (e,i) =>{ console.log(e.target,i) } }
  • 练习:实现表单元素的双向数据绑定
    // input双向数据绑定实现
    import React, { Component } from 'react'
    
    export default class InputBind extends Component {
      constructor () {
        super()
        this.state={
          inputTxt : '初始值'
        }
      }
    
      render() {
        return (
          <div>
            <input type="text" value={this.state.inputTxt} onChange={this.inputHandler}/>
            {this.state.inputTxt}
          </div>
        )
      }
    
      inputHandler = (e) => {
        this.setState({inputTxt: e.target.value})
      }
    }
    

TodoList案例

带你玩转组件通信
src/todoList/index.js

import React, { Component } from 'react'
import TodoHeader from "../todoHeader"
import TodoContent from "../todoContent"
export default class TodoList extends Component {
  constructor () {
    super()
    this.state = {
      todoList : [{
        content: '123',
        isComplated: false
      }],
      inputTxt: ''
    }
  }

  render() {
    return (
      <div className="todoList">
        {/* 将方法通过自定义属性传过去 */}
        <TodoHeader inputTxt={this.state.inputTxt} inputTxtHandler={this.inputTxtHandler} addHandler={this.addHandler}/>
        <TodoContent list={this.state.todoList} finishHandler={this.finishHandler} delHandler={this.delHandler}/>
      </div>
    )
  }

  inputTxtHandler = (txt) => {
    this.setState({
      inputTxt: txt
    })
  }

  addHandler = () => {
    let {todoList,inputTxt} = this.state
    todoList.push({
      content: this.state.inputTxt,
      isComplated: false
    })
    this.setState({
      todoList,
      inputTxt:''
    })
  }

  finishHandler = (i) => {
    let {todoList} = this.state
    todoList[i].isComplated = true

    this.setState({
      todoList
    })
  }

  delHandler = (i) => {
    let {todoList} = this.state
    todoList.splice(i,1)

    this.setState({
      todoList
    })
  }
}

src/todoHeader/index.js

import React, { Component } from 'react'

export default class TodoHeader extends Component {
  constructor () {
    super()
    this.addHandler = this.addHandler.bind(this)
  }

  render() {
    return (
      <div>
        <input value={this.props.inputTxt} onChange={this.inputHandler}/>
        <button onClick={this.addHandler}>添加</button>
      </div>
    )
  }

  inputHandler = (e) =>{
    let target = e.target
    // 直接将父组件的函数传过来,引用数据类型是地址传递,我们只需要在自组件中调用父组件的方法就行了
    // 只是将值传过去,操作方面的还是在父组件中操作
    this.props.inputTxtHandler(target.value)
  }

  addHandler(){
    this.props.addHandler()
  }
}

src/todoItem/index.js

import React, { PureComponent } from 'react'

export default class TodoItem extends PureComponent {
  constructor () {
    super()
    this.finish = this.finish.bind(this)
    this.del = this.del.bind(this)
  }

  render() {
    console.log("渲染了")
    return (
      <div>
        {this.props.content}
        <input readOnly style={{width:'40px'}} value={this.props.isComplated?'已完成':'未完成'}/>
        <button onClick={this.finish}>完成</button>
        <button onClick={this.del}>删除</button>
      </div>
    )
  }

  finish(){
    this.props.finish(this.props.index)
  }

  del(){
    this.props.del(this.props.index)
  }
}

src/todoContent/index.js

import React, { Component } from 'react'
import TodoItem from '../todoItem'

export default class TodoContent extends Component {
  // 爷孙通信
  // 爷爷组件将自己的方法通过prop传给父组件,父组件再通过prop传给子组件,传的是复杂数据类型,是地址传递
  render() {
    return (
      <div>
        {
          this.props.list.map((el,i)=>{
            return (
              <TodoItem key={i} del={this.props.delHandler} content={el.content} isComplated={el.isComplated} index={i} finish={this.props.finishHandler} />
            )
          })
        }
      </div>
    )
  }

  // shouldComponentUpdate(nextProps, nextState){
  //   console.log(JSON.stringify(this.props.list),JSON.stringify(nextProps.list))
  //   if(JSON.stringify(this.props.list) === JSON.stringify(nextProps.list)){
  //     return false
  //   }else{
  //     return true
  //   }
  // }
}

生命周期

挂载
constructor() 初始化构造函数
static getDerivedStateFromProps()
定义state(基于外部的props),接受参数props,需要内部返回{}
生成的state会合并当前的state

export default class Life extends Component {
  render() {
    return (
      
生命周期展示组件 a:{this.state.a}
) } static getDerivedStateFromProps(props){ return{ a: props.aa*2 } } }

render() 渲染 生成虚拟dom
componentDidMount()
渲染完成 可以获取真实Dom
初始化数据,发送ajax在这里发送
类库实例刷新
betterScroll refresh()
swiper observer:true/update()/newSwiper
更新
static getDerviedStateFromProps()
数据(props,state)更新时会再次执行这个函数,即生成的state会随外部传入的props变化而变化
shouldComponentUpdate()
两个参数,nextProps,nextState
用于优化性能,比较更新前后的数据,有bug
通常使用PrueComponent来代替它,但PureComponent只能进行浅比较
render()
getSnapshotBeforeUpdate()
返回快照(更新后的虚拟dom)
componentDidUpdate()
三个参数prevProps, prevState, snapshot
组件数据更新且渲染完成后,可获取最新的dom
卸载
componentWillUnmount()
组件被移除时执行
通常用来清除定时器,或取消一些订阅事件

ref获取元素

// 16版本前
export default class Ref1 extends Component {
  render() {
    return (
      
{this.oDiv = dom}}> 我是一个类组件
) } componentDidMount(){ console.log(this.oDiv) } }
// 16版本后
import React, { Component, createRef } from 'react'

export default class Ref2 extends Component {
  constructor () {
    super()
    // 创建容器
    this.oDiv = createRef()
  }
  render() {
    return (
      <div>
        <div ref={this.oDiv}>
          我也是一个类组件1
        </div>
        <div ref={this.oDiv}>
          我也是一个类组件2
        </div>
      </div>
    )
  }

  componentDidMount(){
    // 一个容器里只能放一个
    console.log(this.oDiv.current)
  }
}

不会渲染的标签


<>

Context

Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。

使用方法:1. 定义context对象 2. 将根组件放在对象的属性Provider组件中,value属性定义提供的数据 3. 在组件使用数据的地方,使用对象的属性Consumer组件,在这组件中以函数的形式返回数据

import React, { Component, createContext } from 'react'
// 当组件所处的树中没有匹配到provider时,读取默认值;否则读取最近的provider,类似于作用域链
const context1 = createContext("我是默认数据")

var A = ()=>{
  return (
    <div>
      我是A组件
    </div>
  )
}

var B = ()=>{
  return (
    <div>
      我是B组件
      <context1.Consumer>
        {
          (v)=>{
            return v
          }
        }
      </context1.Consumer>
    </div>
  )
}

export default class Context1 extends Component {
  render() {
    return (
      <>
        <context1.Provider value={"我是公共数据"}>
          <A/>
          <B/>
        </context1.Provider>
      </>
    )
  }
}

通常使用时进行模块化管理
/Context2/Context2.js

import React, { Component, createContext } from 'react'
let context2 = createContext("我是默认值,组件没有provider包裹")
let {Provider,Consumer: MyConsumer} = context2
// 找到属性Consumer赋值给MyConsumer变量

export default class Context2 extends Component {
  constructor(){
    super()
    this.state = {
      a: 10,
      b: 20
    }
  }

  act = () =>{
    this.setState(()=>{
      return{
        a:20
      }
    })
  }

  render() {
    return (
      <Provider value={{...this.state, act: this.act}}>
        {this.props.children}
      </Provider>
    )
  }
}

export {
  Context2,
  MyConsumer
}

index.js
包裹根元素

import React from 'react'
import ReactDom from 'react-dom'
import App from './App'
import { Context2 } from './Context2/Context2'

ReactDom.render(
  <Context2>
    <App/>
  </Context2>,
  document.querySelector('#root')
)

组件中使用数据
Context2/B.js

import React, { Component } from 'react'
import { MyConsumer } from './Context2'

export default class B extends Component {
  render() {
    return (
      <div>
        我是B组件
        <MyConsumer>
          {
            ({a,act})=>{
              return (
                <>
                  <p>{a}</p>
                  <button onClick={act}>Change</button>
                </>
              )
            }
          }
        </MyConsumer>
      </div>
    )
  }
}

React-Hooks

定义函数式组件时,不好用State和生命钩子函数

state的使用
useState(数据)

// rafce
import React,{useState} from 'react'

// 函数式组件中要想使用state或生命钩子函数
// 必须通过react-hooks
const A = (props) => {
  // console.log(useState(1))  //返回值是一个数组[1,fn]
  // 结构一下
  let [num,setNum] = useState(1)
  return (
    <div>
      我是A函数组件
      {/* 注意要前置运算,里面是赋值操作 */}
      <br/>
      <button onClick={()=>{setNum(++num)}}>+</button>
      <span>{num}</span>
      <button onClick={()=>{setNum(--num)}}>-</button>
    </div>
  )
}

export default A

生命钩子函数(componentDidMount,componentDidUpdate)
useEffect(()=>{})

import React,{useEffect,useState} from 'react'

const B = () => {
  // 初始化arr和接受setArr函数
  let [timer,setTimer] =  useState(1)
  let [arr,setArr] = useState([])
  useEffect(()=>{
    console.log('相当于组件的componentDidMount和componentDidUpdate...')
    if(timer === 1){
      setTimeout(()=>{
        setTimer(null)
        setArr([1,2,3,4,5])
      },3000)
    }
  })
  return (
    <div>
      <ul>
        {arr.map((el,i)=>{
          return (
            <li key={i}>{el}</li>
          )
        })}
      </ul>
    </div>
  )
}

export default B

使用context
useContext(context对象)
contexts.js
导出Provider组件和context对象

import React, { Component, createContext } from 'react'

let myContext = createContext("组件树上没有Provider时显示的默认值")
let {Provider} = myContext

class Contexts extends Component {
  constructor(){
    super()
    this.state = {
      a: 123
    }
  }
  render() {
    return (
      <Provider value={this.state}>
        {this.props.children}
      </Provider>
    )
  }
}

let MyProvider = Contexts

export {
  MyProvider,
  myContext
}

index.js
包裹根元素

import {MyProvider} from './contexts/Contexts'

ReactDom.render(
  <MyProvider>
    <App/>
  </MyProvider>,
  document.querySelector('#root')
)

C.js
使用value

import React,{useContext} from 'react'
import {myContext} from '../contexts/Contexts'
const C = () => {
  let {a} = useContext(myContext)
  return (
    <div>
      我是C组件
      {a}
    </div>
  )
}

export default C

PureComponent

会对传入的props进行浅比较,优化性能

import React, { PureComponent } from 'react'

export default class ListItem extends PureComponent {
  render() {
    console.log('渲染了')
    return (
      <div>
        <h2>{this.props.name}</h2>
        <p>{this.props.age}</p>
      </div>
    )
  }
}

HOC(高阶组件)

函数柯里化 高阶函数

function fn(a){
  return function(b){
    return a+b
  }
}

fn(1)(2) // 3

其实高阶组件本质就是函数,接受一个参数(被修饰组件),返回一个组件

import React, { Component } from 'react'

let Hoc = (Dec) => {
  return class HOC extends Component {
    render() {
      return (
        <>
          
          

XXX公司,版权所有

© 川阮biu

) } } } export default Hoc

News组件中使用

import React, { Component } from 'react'
import Hoc from './HOC'

class News extends Component {
  render() {
    return (
      <div>
        偶是新闻页
      </div>
    )
  }
}

export default Hoc(News)

ES6中的修饰器
https://es6.ruanyifeng.com/#docs/decorator
使用@函数名

@函数名
class xxx {
  ...
}
export default xxx

等价于 export default 函数名(xxx)

在cra中进行非弹出(eject)配置

使用customize-cra
https://www.npmjs.com/package/customize-cra
https://3x.ant.design/docs/react/use-with-create-react-app

  1. 安装customize-cra
npm i customize-cra react-app-rewired -D
  1. 在项目文件夹下创建config-overrides.js文件夹
    在这文件里写配置代码
const {
  // 必需
  override,
  // 开启装饰器
  addDecoratorsLegacy
} = require("customize-cra");

module.exports = override(
  // enable legacy decorators babel plugin
  addDecoratorsLegacy()
);
  1. 修改package.json里面的脚本命令
"scripts": {
  "start": "react-app-rewired start",
  "build": "react-app-rewired build",
  "test": "react-app-rewired test",
  "eject": "react-scripts eject"
}

弹射webpack配置

操作不可逆

npm run eject

默认是使用sass预编译语法,想改成less的话
改下文件 config/webpack.config.js

axios使用

  1. 直接导入使用
import React, { Component } from 'react'
import axios from 'axios'

export default class List extends Component {
  constructor(){
    super()
    this.state = {
      arr: []
    }
  }

  render() {
    return (
      <>
        <ul>
          {
            this.state.arr.map((el,i)=>{
              return (
                <li key={i}>
                  <h4>{el.name}</h4>
                  <p>id:{el.id}</p>
                </li>
              )
            })
          }
        </ul>
      </>
    )
  }

  fetchList(){
    axios.get('https://api.it120.cc/hundan/shop/goods/category/all').then((res)=>{
      this.setState({
        arr: res.data.data
      })
    }).catch((err)=>{
      console.log(err)
    })
  }

  componentDidMount(){
    console.log('组件挂载完成时调用')
    this.fetchList()
  }
}
  1. 通过原型链生成属性到Component上
    但只能在类组件中通过this.$http使用
import React,{Component} from 'react'
import axios from 'axios'

Component.prototype.$http = axios

类组件中使用

fetchList(){
    this.$http.get('xxx').then((res)=>{})
  }

配置反向代理(非弹射下)

  1. 直接在package.json中配置
    “proxy”: “https://api.it120.cc/hundan”
    但这只能写一行

  2. 使用http-proxy-middleware
    https://www.npmjs.com/package/http-proxy-middleware
    安装

npm install --save-dev http-proxy-middleware

src文件夹下创建setupProxy.js文件,在这文件中配置代理

const {createProxyMiddleware: proxy} = require("http-proxy-middleware")
module.exports = function(app){
  app.use(proxy('/baidu',{
    target: "https://news.baidu.com/",
    pathRewrite: {'^/baidu': ''},
    changeOrigin: true
  }));
  app.use(proxy('/api',{
    target: "https://www.fakin.cn/",
    pathRewrite: {'^/api': ''},
    changeOrigin: true
  }));
  app.use(proxy('/hundan',{
    target: "https://api.it120.cc/hundan",
    pathRewrite: {'^/hundan': ''},
    changeOrigin: true
  }));
}

配置反向代理(弹射下)

在 config/webpack.config.js文件中修改proxy属性

proxy: {
  '/hundan': {
    target: 'https://api.it120.cc',
    ws: false,
    changeOrigin: true,
    pathRewrite: {
      '^hundan':''
    }
  }
}

redux

集中式状态管理工具 类似于 vuex
项目 比较 大 (数据 比较 复杂)
注意:
vuex是基于vue
redux 是和 react 解耦的(可以在任意一个库中使用)
https://www.redux.org.cn/ 中文文档


每一次处理数据,
先reducer调用,调用时,store会将state作为参数传入,一般还有行为(action),action会未作为第二个参数传入,reducer 会根据action 处理 state(store传入的形参)
处理state时,需要 深拷贝(state是store传入,state是对象,传递的是引用),
reducer根据action改变深克隆的这个 state 然后返回给store,store自己去更新数据

注意:
  每一次不管是拿数据 还是 提交数据 还是改变数据
  组件 向仓库 提交 action store 都会先调用管理系统 reducer,将现在的state,和action都作为参数 给reducer reducer 根据action.type去修改 state(深克隆的),改完后将值返回给 store store再存起来(state数据更新)

redux-dev-tools

import  { createStore } from "redux"
// 创建一个仓库
// 引入 reducer
import reducer from "./reducer"

const store = createStore(reducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
  )
export default store

如何 创建一个 store

npm i redux -S

src/store/index.js

      import { createStore } from 'redux'
      import reducer from './reducer'  //store管理系统

      const store = createStore(reducer)

    reducer.js
      const defaultState = {
        arr:xxx
      }

      //定义管理系统
      const reducer = (state=defaultState,action)=>{
        return state;
      }

组件中如何 使用 store

  • 获取state
    store.getState()
import React, { Component } from 'react'
import store from './store'
export default class News extends Component {
  constructor(){
    super()
    this.state = store.getState()
  }
  render() {
    return (
      <div>
        我是新闻页
        <ul>
          {
            this.state.arr.map((el,i)=>{
              return (
                <li key={i}>{el}</li>
              )
            })
          }
        </ul>
      </div>
    )
  }
}
  • 设置state
    store.dispatch(action)
    通过调用store.dispatch()提交action,他会再次触发reducer,根据action里面的type做相应处理,更改state时,要进行深拷贝,不能直接更改state,应该返回个新的statestore自己更改。注意,返回一个新的state后,storestate更改了,而我们在constructor里面使用的state是通过store.getState()获取的,constructor这个生命周期钩子函数只会执行一遍,我们要想获取最新的state,可以使用store.subscribe(()=>{'store里的state更改了'})

action格式是一个对象,通常设置属性type表示什么行为,datavalue保存action中携带的数据

下面以todoList案例介绍store使用

src/TodoList.js

import React, { Component } from 'react'
import store from './store'
import './myStyle.scss'
export default class TodoList extends Component {
  constructor(){
    super()
    this.state = {
      inputTxt:'',
      ...store.getState()
    }
    store.subscribe(this.updater)
  }
  render() {
    return (
      <div>
        <header>
          <input type="text" placeholder="请输入..." value={this.state.inputTxt} onChange={this.inputHandler}/>
          <button onClick={this.clickHandler}>增加</button>
        </header>
        <ul className="list">
          {
            this.state.arr.map((el,i)=>{
              return (
                <li key={i}>
                  {el.content}
                  <button onClick={()=>{this.delHandler(i)}}>删除</button>
                  <button onClick={()=>{this.changeHandler(i)}}>{el.isComplated?'已完成':'未完成'}</button>
                </li>
              )
            })
          }
        </ul>
      </div>
    )
  }

  updater = ()=>{
    this.setState({
      ...store.getState()
    })
  }

  inputHandler = (e) =>{
    let target = e.target
    this.setState({
      inputTxt: target.value
    })
  }

  clickHandler = () =>{
    store.dispatch({
      type: 'addListItem',
      value: this.state.inputTxt
    })
    this.setState({
      inputTxt: ''
    })
  }

  delHandler = (index) => {
    // react里面由confirm方法,所以要加window.,别省略了
    if(window.confirm('确定删除吗?')){
      store.dispatch({
        type: "delListItem",
        index
      })
    }
  }

  changeHandler = (index) => {
    store.dispatch({
      type: 'changeListItem',
      index,
      state: !this.state.arr[index].isComplated
    })
  }
}

src/store/index.js

import {createStore} from 'redux'
import reducer from './reducer'

let store = createStore(reducer)

export default store

src/store/reducer.js

let defaultState = {
  arr: []
}

let reducer = (state=defaultState,action) => {
  //注意要深拷贝,不能直接修改state
  let newState = JSON.parse(JSON.stringify(state))
  switch(action.type){
    case "addListItem":
      newState.arr.push({
        content: action.value,
        isComplated: false
      })
      break;

    case "delListItem":
      newState.arr.splice(action.index,1)
      break;
    
    case "changeListItem":
      newState.arr[action.index].isComplated = action.state
      break;
      
    default:
      break;
  }
  return newState
}

export default reducer

上面的方法中,会发现一些问题

  1. action直接写在dispatch中,不方便复用,通常以函数返回对象的形式定义action,模块化管理这些action
  2. type是字符串,书写时可能出错,通常写成常量

src/store/actionCreator.js

import {ADD_LISTITEM,DEL_LISTITEM,CHANGE_LISTITEM} from './actionType'
const addListItem = (txt) => {
  return {
    type: ADD_LISTITEM,
    value: txt
  }
}

const delListItem = (index) => {
  return {
    type: DEL_LISTITEM,
    index
  }
}

const changeListItem = ({index,state}) => {
  return {
    type: CHANGE_LISTITEM,
    index,
    state
  }
}

export {
  addListItem,
  delListItem,
  changeListItem
}

src/store/actionType.js

var ADD_LISTITEM = 'addListItem';
var DEL_LISTITEM = "delListItem";
var CHANGE_LISTITEM = "changeListItem";

export {
  ADD_LISTITEM,
  DEL_LISTITEM,
  CHANGE_LISTITEM
}

异步action

redux插件实现
redux-saga (es6 generator语法)
redux-thunk 讲这个

安装redux-thunk

npm i redux-thunk -S

直接使用,不保留redux-dev-tool
src/store/index.js

import {createStore,applyMiddleware} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'

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

export default store

src/store/actionCreator.js

const addListItem = (txt) => {
  return {
    type: ADD_LISTITEM,
    value: txt
  }
}

// 定义异步action
const asyncAdd = (txt) =>{
  // 返回的是一个函数,不是对象
  // store.dispatch()会判断参数为函数的情况,这函数的参数就为dispatch
  return (dispatch)=>{
    setTimeout(function(){
      dispatch(addListItem(txt))
    },2000)
  }
}

组件中使用异步action

store.dispatch(asyncAdd(this.state.inputTxt))

保留redux-dev-tool(createStore函数只接受两个函数)

import {createStore,applyMiddleware,compose} from 'redux'
import reducer from './reducer'
import thunk from 'redux-thunk'

// 组合工具函数compose
let enhancer = compose(
  applyMiddleware(thunk),
  (window &&window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__) ? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__() : (f) => f)

let store = createStore(reducer,enhancer)

export default store

reducer模块化管理

src/store/todoList/
actionCreator.js
actionTypes.js

reducer.js

export default todoReducer

src/store/reducer.js

import {combineReducers} from 'redux'
import todoReducer from './todoList/reducer'

let rootReducer = combineReducers({
  todoReducer
})
// store中的state会变成多层

export default rootReducer

组件中使用todoList的state

this.state = {
  inputTxt:'',
  ...store.getState().todoReducer
}

react-redux

redux和react是解耦的,不方便使用(store.getState()、store.dispatch(action))
搭配react使用 原理:高阶组件、context
通过props使用state和dispatch action

https://www.redux.org.cn/docs/react-redux/api.html#api

npm i react-redux -S

将store中的state以props形式传递于整个组件树中
src/index.js

import React from 'react'
import ReactDom from 'react-dom'
import App from './App'

import store from './store'
import {Provider} from 'react-redux'

ReactDom.render(
  <Provider store={store}>
    <App/>
  </Provider>,
  document.querySelector("#root")
)

组件中使用state和派发action就不需要基于store了,也不需要store.subsrcibe来配置监听state的变化
为了更方便的使用dispatch和获取state,使用connect方法
src/TodoList.js

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { addListItem, delListItem, changeListItem, asyncAdd } from './store/todoList/actionCreator'
import './myStyle.scss'
class TodoList extends Component {
  constructor(){
    super()
    this.state = {
      inputTxt:''
    }
  }
  render() {
    return (
      <div>
        <header>
          <input type="text" placeholder="请输入..." value={this.state.inputTxt} onChange={this.inputHandler}/>
          <button onClick={this.clickHandler}>增加</button>
        </header>
        <ul className="list">
          {
            this.props.arr.map((el,i)=>{
              return (
                <li key={i}>
                  {el.content}
                  <button onClick={()=>{this.delHandler(i)}}>删除</button>
                  <button onClick={()=>{this.changeHandler(i)}}>{el.isComplated?'已完成':'未完成'}</button>
                </li>
              )
            })
          }
        </ul>
      </div>
    )
  }

  inputHandler = (e) =>{
    let target = e.target
    this.setState({
      inputTxt: target.value
    })
  }

  clickHandler = () =>{
    // store.dispatch(addListItem(this.state.inputTxt))
    this.props.asyncAdd(this.state.inputTxt)

    this.setState({
      inputTxt: ''
    })
  }

  delHandler = (index) => {
    if(window.confirm('确定删除吗?')){
      this.props.delListItem(index)
    }
  }

  changeHandler = (index) => {
    this.props.changeListItem({index,state: !this.props.arr[index].isComplated})
  }
}
const mapStateToProps = (state) => {
  return{
    arr: state.todoReducer.arr
  }
}

const mapDispatchToProps = (dispatch) => {
  return {
    asyncAdd: (v)=>{
      dispatch(asyncAdd(v))
    },
    delListItem: (i)=>{
      dispatch(delListItem(i))
    },
    changeListItem: (params)=>{
      dispatch(changeListItem(params))
    }
  }
}

export default connect(mapStateToProps,mapDispatchToProps)(TodoList)

redux持久化存储实现

localStorage、sessionStorage
在reducer.js中进行存储localStorage
src/store/todoList/reducer.js

import {ADD_LISTITEM,DEL_LISTITEM,CHANGE_LISTITEM} from './actionType'
let storageState = localStorage.getItem('todoState')?JSON.parse(localStorage.getItem('todoState')):{arr: []}

let defaultState = storageState

let todoReducer = (state=defaultState,action) => {
  let newState = JSON.parse(JSON.stringify(state))
  switch(action.type){
    case ADD_LISTITEM:
      newState.arr.push({
        content: action.value,
        isComplated: false
      })
      break;

    case DEL_LISTITEM:
      newState.arr.splice(action.index,1)
      break;
    
    case CHANGE_LISTITEM:
      newState.arr[action.index].isComplated = action.state
      break;
      
    default:
      break;
  }
  localStorage.setItem("todoState",JSON.stringify(newState))
  return newState
}

export default todoReducer

react路由

安装

npm i react-router-dom -S

react中万物皆组件
学习react路由中比较重要的几个组件
用来包裹根组件
配置路由规则
解决贪婪匹配问题,会显示多个路由
实现重定向功能,404页面,根路由重定向到首页
渲染到页面为a标签,可实现跳转指定路由的功能

exact 这是组件的一个常用属性,用于解决模糊匹配路径的问题,实现精确匹配

使用
index.js
包裹根组件,可以在整个组件树中使用路由功能,同时路由组件的props会携带一些路由信息

mport {HashRouter as Router} from 'react-router-dom'

ReactDom.render(
  <Router>
    <App/>
  </Router>,
  document.querySelector("#root")
)

App.js

import React from 'react'
// 这些组件都需要引入使用
import {Switch,Route,Redirect,Link,NavLink} from 'react-router-dom'
import Home from './Home'
import NotFound from './NotFound'
import Detail from './Detail'
import Cart from './Cart'
import News from './News'

const App = () => {
  return (
    <div>
      <Link to="/home">到首页</Link>&nbsp;&nbsp;
      <Link to="/news">到新闻页</Link>&nbsp;&nbsp;
      <Link to="/cart">到购物车</Link>
      <hr/>
      <NavLink to="/home" activeStyle={{color: "pink"}} activeClassName="current">到首页</NavLink>&nbsp;&nbsp;
      <NavLink to="/news" activeStyle={{color: "pink"}} activeClassName="current">到新闻页</NavLink>&nbsp;&nbsp;
      <NavLink to="/cart" activeStyle={{color: "pink"}} activeClassName="current">到购物车</NavLink>
      <Switch>
        <Route path="/home" component={Home}/>
        <Route path="/detail/:id" component={Detail}/>
        <Route path="/cart" component={Cart}/>
        <Route path="/news" component={News}/>
        <Route path="/404" component={NotFound}/>
        <Redirect to="/home" from="/" exact/>
        <Redirect to="/404"/>
      </Switch>
    </div>
  )
}

export default App

嵌套路由

写在父路由模板组件中
父路由组件(Route)不能加exact属性

父路由模板组件
News.js

import React, { Component } from 'react'
import {Switch,Route} from 'react-router-dom'
import NativeNews from './NativeNews'
import AbroadNews from './AbroadNews'

export default class News extends Component {
  render() {
    return (
      <div>
        我是新闻页
        <hr/>
        <Switch>
          <Route path="/news/nativeNews" component={NativeNews}/>
          <Route path="/news/abroadNews" component={AbroadNews}/>
        </Switch>
      </div>
    )
  }
}

App.js

import React from 'react'
import News from './News'
import {Route,Redirect,Switch,Link,NavLink} from 'react-router-dom'

const App = (props) => {
  console.log(props)
  return (
    <div>
      <Link to="/news">到新闻页</Link>
      <hr/>
      <NavLink to="/news" activeClassName="current" activeStyle={{color:"pink"}}>到新闻页</NavLink>
      <Switch>
        {/* news有子路由,不可以加 exact精确查询,要先进入父组件*/}
        <Route path="/news" component={News}/>
      </Switch>
    </div>
  )
}

export default App

路由传参

  1. 动态路由传参
  2. search
  3. params
  4. state

12优缺点 显式传参(地址栏显示),刷新不消失
34优缺点 隐式传参(不在地址栏上显示),刷新消失

  • 动态路由传参
<Route path="/detail/:id" component={Detail}/>

组件中使用

console.log(this.props.match.params)//{id: "18"}

地址栏显示

http://localhost:3000/#/detail/18
  • search
    直接地址栏后拼接
<Link to="/cart?user=Jack">到购物车</Link>

组件中获取使用

console.log(this.props.history.location.search) //?user=Jack
console.log(this.props.location.search) //?user=Jack
console.log(qs.parse(this.props.location.search.split('?')[1].toString())) //{user: "Jack"}
  • params
    Link和NavLink组件的to属性可以接受两种类型的参数StringObject
    为对象时就可以设置一些参数
<Link to={{pathname:'/cart',params:{a:1}}}>到购物车</Link>
// 组件中使用
console.log(this.props.location.params) //{a:1}
  • state
<Link to={{pathname:'/cart',state:{a:1}}}>到购物车</Link>
// 组件中使用
console.log(this.props.location.state) //{a:1}

编程式导航

Link、NavLink to 是声明式
组件中使用
this.props.history.go(n)
this.props.history.goBack()
this.props.history.goForward()
this.props.history.push(path, state)
this.props.history.replace(path, state)

非路由组件中props没有路由信息

使用高阶组件withRouter

import {withRouter} from 'react-router-dom'
...
export default withRouter(组件名)

登录权限判断,实现vue中的路由守卫

使用Route组件的属性render,属性值为一个函数
react比较灵活,所有东西要自己写

<Route path="/my" render={(routeProps)=>{
  // console.log(routeProps)
  return (
    <>
      {
        1===1?
        // 
        <NeedLogin {...routeProps}/>
        :
        <Login/>
      }
    </>
  )
}}/>

通过rendercomponent实现渲染的组件props上没有路由信息,就算使用withRouter也没用,我们必需要通过props属性传过去,render函数的参数就是routeProps,就是路由信息,传过去就行了

<NeedLogin {...routeProps}/>

路由懒加载

原理webpack的按需引入

npm i react-loadable -S

路由中使用
src/routes/index.js

import Loadable from 'react-loadable';
import Loading from '@/components/Loading';
// 上面不需要导入了
const baseRoutes = [{
    path: '/login',
    component: Loadable({
      loader: () => import('@/views/Login'),
      loading: Loading,
    })
  },
  {
    path: '/404',
    component: Loadable({
      loader: () => import('@/views/NotFound'),
      loading: Loading,
    })
}];

自定义loading组件或使用antd的spin组件
src/components/Loading/index.js

import React, { Component } from 'react'
import './style.scss'
export default class index extends Component {
  render() {
    return (
      <div className="bg">
        <div className="dotBox">
          <p>
            <span></span>
            <span></span>
            <span></span>
            <span></span>
          </p>
          <p>
            <span></span>
            <span></span>
            <span></span>
            <span></span>
          </p>
        </div>
      </div>
    )
  }
}

样式
src/components/Loading/style.scss

.bg{
  position: fixed;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 999;
  background:rgba($color: #000000, $alpha: 0.8);
  .dotBox{
    width: 120px;
    height: 120px;
    top: 50%;
    left: 50%;
    margin-top: -60px;
    margin-left: -60px;
    position: absolute;
    p{
      width: 120px;
      height: 120px;
      position: absolute;
      span{
        display: block;
        width: 24px;
        height: 24px;
        border-radius: 12px;
        background: white;
        position: absolute;
        animation: twinkle 1.5s linear infinite;
        &:nth-of-type(2){
          top: 0;
          right: 0;
        }
        &:nth-of-type(3){
          bottom: 0;
          right: 0;
        }
        &:nth-of-type(4){
          bottom: 0;
          left: 0;
        }
      }
      &:nth-of-type(1){
         span{
            &:nth-of-type(1){
              animation-delay: -0.4s;
            }
            &:nth-of-type(4){
              animation-delay: -0.8s;
            }
            &:nth-of-type(3){
              animation-delay: -1.2s;
            }
            &:nth-of-type(2){
              animation-delay: -1.6s;
            }
         }
      }
      &:nth-of-type(2){
        transform: rotate(45deg);
        span{
          &:nth-of-type(1){
            animation-delay: -0.2s;
          }
          &:nth-of-type(4){
            animation-delay: -0.6s;
          }
          &:nth-of-type(3){
            animation-delay: -1s;
          }
          &:nth-of-type(2){
            animation-delay: -1.4s;
          }
        }
      }
    }
  }
}

@keyframes twinkle{
  from{transform: scale(0);}
  50%{transform: scale(1);}
  to{transform: scale(0);}
}

你可能感兴趣的:(reactjs)