架构-react阶段笔记

1. 什么是React?
    React 是一个用于构建用户界面的JavaScript库 核心专注于视图,目的实现组件化开发
2. 组件化的概念
    我们可以很直观的将一个复杂的页面分割成若干个独立组件,每个组件包含自己的逻辑和样式 再将这些独立组件组合完成一个复杂的页面。 这样既减少了逻辑复杂度,
    又实现了代码的重用
    可组合:一个组件可以和其他的组件一起使用或者可以直接嵌套在另一个组件内部
    可重用:每个组件都是具有独立功能的,它可以被使用在多个场景中
    可维护:每个小的组件仅仅包含自身的逻辑,更容易被理解和维护
3.搭建React开发环境
    cnpm i create-react-app -g
    create-react-app zhufeng2020react
    cd zhufeng2020react
    npm start
4.JSX
    1)什么是JSX
        JSX其实只是一种语法糖,是一种JS和HTML混合的语法,将组件的结构、数据甚至样式都聚合在一起定义组件,最终会通过babeljs转译成React.createElement()语法
        React元素(虚拟dom)是构成React应用的最小单位,用来描述你在屏幕上看到的内容,React元素事实上是普通的JS对象,
        ReactDOM来确保浏览器中的DOM数据和React元素保持一致
        //jsx最终会被转换成React.createElement()语法得到react元素也就是虚拟dom
        //虚拟dom会在经过ReactDOM.render()产生真实dom渲染到页面
        let element = (
          <h1 className="title" style={{color:'red'}}>
            <span>hello</span>world
          </h1>
        let element = React.createElement("h1", {
          className: "title",
          style: {
            color: 'red'
          }
        }, React.createElement("span", null, "hello"), "world");
    2)JSX的执行过程
        1. 我们写代码的时候写的JSX `<h1>hello</h1>`
        2. 打包的时候,会调用webpack中的babel-loader把JSX写法转换成JS写法 createElement
        3. 我们在浏览器里执行createElement,得到虚拟DOM,也就是React元素,它是一个普通的JS对象,描述了你在界面上想看到的DOM元素的样式
        4. 把React元素(虚拟DOM)给了ReactDOM.render,render会把虚拟DOM转成真实DOM,并且插入页面
    3)JSX表达式
        jsx表达式可以把一些表达式放在大括号里
        let title = 'hello';
        let element = <h1>{title}</h1>
    4)className/style
        属性名不能是JS的关键字 class=>className  style=>字符串变成对象
        let element = <h1 className={title} style={{color:'red'}}></h1>
    5)循环
        let names = ['大毛','二毛','三毛'];
        let lis = names.map(name=><li>{name}</li>);
        //解析时碰见数组自动循环渲染
        <ul>
            {lis}
        </ul>
    6)更新元素渲染
        React元素都是不可变的。当元素被创建之后,你是无法改变其内容或属性的。一个元素就好像是动画里的一帧,它代表应用界面在某一时间点的样子
        更新界面的唯一办法是创建一个新的元素,然后将它传入ReactDOM.render()方法
    7)函数组件
        function Welcome(props){
          return <h1 name={props.name}></h1>
        }
        //参数1=函数的话 说明它是一个函数组件的元素
        let element = React.createElement(Welcome,{name:'zhufeng'});
    8)类组件
        class Welcome extends React.Component{  // this.props={name:'zhufeng'}
          render(){
            return <h1>hello,{this.props.name}</h1>;
          }
        }
        let element = <Welcome name="zhufeng"/>
    9)类组件是如何渲染的?
        1.定义一个类组件React元素
        2.创建类组件的实例 new Welcome(props),构造函数中执行this.props = props;
        3.调用实例的render方法得到一个react元素
        4.把这个React元素转换成真实的DOM元素并插入到页面中去
    10)属性对象和状态对象
        1.组件的数据来源有两个地方,分别是属性对象(props)不能改和状态对象(state)可以改,属性是父组件传递过来的(默认属性,属性校验),状态是自己内部的,
        改变状态唯一的方式就是setState,属性和状态的变化都会影响视图更新,组件会重新调用一次render方法,得到新虚拟DOM,进行DOM更新
        2.类组件和函数组件都有属性,状态只能用在类组件里
            class Clock extends React.Component{
                constructor(props){
                    super(props);
                    this.state = {date:new Date()};
                }
                tick=()=>{
                    this.setState({date:new Date()});
                }
            }
        3.出于性能考虑,State的更新可能是异步的,React 可能会把多个 setState()调用合并成一个调用当你在事件处理函数中执行setState,组件并不会立刻渲染,
             而是先把更新存起来,等事件处理函数执行完了再会批量更新
              //react管的到的地方都是异步的,在事件处理函数中或生命周期函数中批量更新的 异步的
              handleClick=()=>{
                      //number的原值是0,由于更新state是异步的所以打印为0
                      //setState中放对象的话this.state.number都是基于原值计算的,所以最终只会加1,如果变成函数那么上一个函数的state
                      //会传到下一个函数下所以会加2
                    //this.setState({number: this.state.number++}); 
                    this.setState(state=>({number: this.state.number++})); 
                    console.log(this.state.number) //0
                    //this.setState({number: this.state.number++);     
                    this.setState(state=>({number: this.state.number++}));             
              }
              //react管不到的地方都是同步的,比如说setTimeout,因为它为交给浏览器宏任务,react控制不到
              handleClick = ()=>{
                    setTimeout(()=>{
                            this.setState({number: this.state.number++}); 
                            console.log(this.state.number);//1
                            this.setState({number: this.state.number++}); 
                            console.log(this.state.number);//2
                        });
                    }
        4.当你调用 setState() 的时候,React 会把你提供的对象合并到当前的 state
            this.state = {
                name: 'zhufeng',
                number: 0
            };
            //name属性不会被覆盖
            this.setState({ number: state.number + 1 });
    11)this问题
        1.用箭头函数 首选方案
            handleClick = (amount)=>{}
        2.如果不使用箭头函数,普通函数中的this=undefined,可在render使用匿名函数
            //因为render函数中的this执行实例,所以实例调用,this指向的就是实例
            <button onClick={()=>this.handleClick()}>+</button>
        3.可以在构造函数中重写this.handleClick,绑死this指针
            this.handleClick = this.handleClick.bind(this)
        4.如何要传参数,只能使用匿名函数
            <button onClick={()=>this.handleClick(3)}>+</button>
    12)合成事件
        react17以前,标签上的事件都会绑定到document上,17以后都绑定到root根节点上,通过事件冒泡触发,同时事件对象event也被重写了
        为什么需要合成事件,作用是什么?
            1.可以实现批量更新
            2. 可以实现事件对象的缓存和回收(event.persist()不销毁事件对象)
    13)Context上下文
        在一个典型的 React 应用中,数据是通过 props 属性自上而下(由父及子)进行传递的,但这种做法对于某些类型的属性而言是极其繁琐的,
        Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props
        import React from 'react';
        let ThemeContext = React.createContext();
        //父组件使用ThemeContext.Provider共享数据vakue,子组件Header和Main可以直接使用ThemeContext.Consumer加一个箭头函数消费数据
        <ThemeContext.Provider value={value}>
            <div style={style}>
                <Header/>
                <Main/>
            </div>
        </ThemeContext.Provider>
        <ThemeContext.Consumer>
            {
                (value)=>(
                    <div style={{border:`5px solid ${value.color}`,padding:'5px'}}>
                        Main
                        <Content/>
                    </div>
                )
            }
        </ThemeContext.Consumer>
       14)高阶组件(属性代理和反向继承)
           高阶组件就是一个函数,传给它一个组件,它返回一个新的组件,高阶组件的作用其实就是为了组件之间的代码复用
           1.属性代理(新组件和老组件是组合关系,会有两个类实例)
               //loading函数执行的返回值就是高阶组件,message和高阶组件的属性都可以在老组件上使用
               const loading = message => OldComponent =>{
                   //NewComponent可以不写
                return class NewComponent extends React.Component{
                    render(){
                        const extraProps = {
                            hide : ()=>{},
                            show : ()=>{}
                        }
                        let newProps = {...this.props,...extraProps};                    
                        return (
                            //NewComponent组件的所有数据都可以传递给OldComponent组件
                            <OldComponent {...newProps}/>
                        )
                    }
                }
            }
            class Hello extends React.Component{
                render(){
                    return (
                        <div>
                            <button onClick={this.props.show}>显示{{message}}</button>
                        </div>
                    )
                }
            }
            let LoadingHello = loading('加载中....')(Hello);
            ReactDOM.render(<LoadingHello title="标题"/>,document.getElementById('root'));
        2.反向继承(新组件和老组件是继承关系,只会有一个类实例)
            反向继承是基于反向继承,我们可以拦截父组件生命周期,state还有渲染过程,比如说这个 Button是antdesign提供的标准组件,我们改不了,
            那应该如何修改它? 给他加儿子数字,加点击事件,让儿子数字加1
            const wrapper = OldComponent => { 
                return class NewComponent extends OldComponent{
                    state = {number:0}
                    componentWillMount(){
                        super.componentWillMount();
                    }
                    handleClick = (event)=>{
                        this.setState({number:this.state.number+1});
                    }
                    render(){
                        let oldElement = super.render();
                        let newProps = {
                            ...oldElement.props,
                            onClick:this.handleClick
                        }
                        //cloneElement()克隆一个组件并给组件增加属性和孩子
                        return React.cloneElement(oldElement,newProps,this.state.number);
                    }
                }
            }
            class Button extends React.Component{
                state = {name:'按钮'}
                componentWillMount(){
                    console.log('OldComponent componentWillMount');
                }
                render(){
                    return (
                        <button name={this.state.name} title={this.props.title}/>
                    )
                }
            }
            let LoadingHello = wrapper(Button);
            ReactDOM.render(<LoadingHello title="标题"/>,document.getElementById('root'));
    15)render-props
        render prop是指一种在 React 组件之间共享代码的简单技术
        class MouseTracker extends React.Component{
            render(){
                return (
                    <div>
                       {this.props.render(this.state)}
                    </div>
                )
            }
        }
        ReactDOM.render(
        <MouseTracker  render={
                props=>(
                    <>
                      <h1>移动鼠标</h1>
                      <p>当前的鼠标位置是x={props.x},y={props.y}</p>
                    </>
                )
            }/>,root);
    16)PureComponent
        组件状态和属性没有发生改变时,不在重新渲染组件,内部实现了shouldComponentUpdate()方法比较了属性和状态    
    
5.react源码
    1)虚拟DOM和render函数
        1.在react.js文件中createElement()方法根据用户传入的参数产生虚拟dom
        2.在react-dom.js文件中render()/createDOM()方法把虚拟dom变成真实dom在插入到指定容器
        3.在createDOM()方法中根据虚拟dom的type创建标签(字符串类型说明是个原生标签),在updateProps()方法中更新元素除了children的其它属性
        4.在createDOM()方法中如果children是字符串或者数字,那么创建文本插入到元素中,如果children是对象说明是个标签,那么调用render()
             方法循环解析虚拟dom插入到父元素中,如果children是数组,那么调用reconcileChildren()方法循环render()方法即可
    2)函数组件
        在react-dom.js文件中createDOM()方法如果type是函数,那么调用mountFunctionComponent()方法挂载组件,调用函数拿到返回值调用
        createDOM()方法参数真实dom渲染
    3)类组件
        在react-dom.js文件中createDOM()方法如果type是函数并且有isReactComponent属性说明是类组件,那么调用mountClassComponent()方法挂载组件,
        mountClassComponent()方法中new type(props)创建类实例,再调用实例的render方法得到一个react元素,在调用createDOM()方法参数真实的DOM元素
        并插入到页面中去
    4)事件绑定和同步更新
        1.react-dom.js文件中updateProps()方法中如果属性以on开头,那么就给dom添加事件即可
        2.Component.js文件中setState()方法中调用this.updater.addState()方法将状态维护到pendingStates数组中,当非批量更新状态也就是同步更新
             调用updateComponent()方法更新组件,调用getState()方法得到最新状态给类组件,再调用forceUpdate()/updateClassComponent()方法拿新dom
             替换老dom
    5)合成事件实现异步更新(批量更新)
        1.react-dom.js文件中updateProps()方法中通过addEvent()方法将标签事件绑定到document上,在通过event.js文件中的dispatchEvent()/createSyntheticEvent()
             方法实现异步更新和重写事件对象event
    6)实现ref
        1.react.js文件中createRef()方法返回{current:null}
        2.在createElement()方法中将config中的ref单独返回
        3.在react-dom.js文件中createDOM()方法中将真实dom挂载ref上
    7)生命周期函数
        1.在react-dom.js文件中mountClassComponent()方法中创建类实例后调用componentWillMount()方法,产生真实dom后调用componentDidMount()方法
             其实应该在真实dom插到页面中在调用,这里没有完全出来好
        2.在Component.js文件shouldUpdate()方法如果组件有shouldComponentUpdate()方法并且返回值为true接着调用forceUpdate()往下走
        3.在forceUpdate()方法中调用componentWillUpdate(),更新完后调用componentDidUpdate()方法
    8)diff算法和剩余生命周期
        1.在react-dom.js文件中mountClassComponent()方法中在vdom上挂着类实例和老的真实dom,在实例上挂着老的真实和虚拟dom,虚拟dom上面挂着实例和真实dom
        2.在Component.js文件中的forceUpdate()方法更新组件前调用compareTwoVdom()方法进行diff比较老旧虚拟dom
        3.compareTwoVdom()中如果新旧虚弥dom都为null那么不操作,老有新没有那么把老的真实dom移除掉并且调用componentWillUnmount()生命周期函数
             旧有新没有那么产生新真实dom插入页面即可,新老type不一样也用新的替换老的(父级不一样就全部替换),如果新旧都有那么调用updateElement()方法做diff算法
        4.如果标签是个string类型说明是个原生标签那么调用updateProps()/updateChildren()方法更新属性和孩子,在updateChildren()方法中继续调用compareTwoVdom()递归更新
             真实dom
        5.如果标签是个类组件那么调用updateClassInstance()方法对比类组件,此处调用componentWillReceiveProps()生命周期,接着调用实例的updater.emitUpdate()/forceUpdate()方法
             对比类组件虚弥dom,如果是个函数组件调用updateFunctionComponent()方法拿到新旧函数组件的renderVdom调用compareTwoVdom()方法进行比较更新
    8)完整diff算法(10.zhufengreact202110)
        1.basic/src/react-dom.js文件的compareTwoVdom方法
        2.如果如果老的是null新的也是null,那么什么都不操作
        3.如果老有,新没有,那么删除老节点
        4.如果说老没有,新的有,新建DOM节点
        5.如果标签类型不同,也不能复用了,也需要把老的替换新的
        6.如果类型一致
            1)节点是文本的话直接新文本替换老文本
            2)如果是标签的话,先更新属性,在递归更新孩子
                1.遍历孩子时,构建老map key虚拟DOM的key,值虚拟DOM
                2.循环拿新虚拟dom可老map比较,通过lastPlacedIndex值,更新复用的节点,删除移动和多余的节点
                3.接着再把移动的和新增的节点放到对应的位置即可
            3)函数组件调用函数组件更新逻辑,类组件调用类组件更新逻辑
        7.总结                
            1)只比较同级节点
            2)只比较同类型节点
            3)可以通过key标识唯一元素
            4)react为什么只能进行头头比较,不能像vue一样去比较,因为react用的是链表结构,不能通过索引去比较
    9)新版生命周期
        在react-dom.js文件的mountClassComponent()方法调用实例render()方法前调用getDerivedStateFromProps()生命周期,在Component.js文件shouldUpdate()方法shouldComponentUpdate()
        生命周期前调用getDerivedStateFromProps()生命周期,forceUpdate()方法中当componentDidUpdate()生命周期前调用getSnapshotBeforeUpdate()生命周期
    10)Context上下文
        在react.js文件中增加createContext()方法返回一个对象包含Provider/Consumer函数组件,Provider提供的数据可以被Consumer消费
    11)React.cloneElement()
        在react.js文件中cloneElement()方法接收一个组件返回一个新组件
    12)Fragment
        在react.js文件中createDOM()方法如果type是reactFragment类型,那么创建一个文档碎片document.createDocumentFragment()即可
    13)PureComponent
        Component.js文件中定义PureComponent类继承Component类,内部实现了shouldComponentUpdate()方法比较了属性和状态,组件状态和属性没有发生改变时,不在重新渲染组件
        在react.js文件中导出PureComponent类    
    14)总结
        1.在react.js文件中createElement()方法根据用户传入的参数产生虚拟dom
        2.在react-dom.js文件中render()/createDOM()方法把虚拟dom变成真实dom在插入到指定容器
        3.如果类型是类组件调用mountClassComponent()方法产生真实dom,如果类型是函数组件调用mountFunctionComponent()方法产生真实dom
        4.类虚拟dom记录着类实例/真实dom,实例上记录着类虚拟dom/老的实例虚拟dom/真实dom,类实例虚拟dom上记录着真实dom,最终解析到原生标签的时候,虚拟dom上都会记录着真实dom          
        5.如果类型是原生标签,那么调用document.createElement()创建标签,接着调用updateProps()添加标签属性,接下来更儿子,儿子如果是文本就直接添加文本,如果是单React元素节点  
             那么调用render()方法将节点渲染到标签上,如果孩子是个数组,那么调用reconcileChildren()方法循环调用render()方法即可
        6.同步更新
            1)event.js文件中调用类实例setState()方法时将状态放在更新器pendingStates数组中,调用emitUpdate()/updateComponent()/forceUpdate()方法触发更新
                 更新老的state状态,调用compareTwoVdom()方法拿着老的真实dom父节点/老的虚拟dom/新的虚拟dom进行比较更新
            2)比较中如果新旧节点都没有,什么都不做;老没有新有,那么拿新的真实dom插入到老真实dom的位置;如果老有新没有,那么删除老真实dom;如果新老都有并且标签类型不一样,那么
                 拿新的dom替换老的;否则说明标签类型一样就调用updateElement()方法走diff算法
            3)如果是文本,那么拿新文本替换老文本;如果是标签,那么调用updateProps()/updateChildren()方法更新属性和孩子,更新孩子是循环调用compareTwoVdom()方法递归比较;
                 如果是类组件,那么调用updateClassInstance()/classInstance.updater.emitUpdate()方法递归更新;如果是函数组件,那么拿到函数组件的新老虚拟dom继续调用compareTwoVdom()方法递归比较;
        7.合成事件实现异步更新(批量更新)
            react-dom.js文件中updateProps()方法中通过addEvent()方法将标签事件绑定到document上,在通过event.js文件中的dispatchEvent()/createSyntheticEvent()
                  方法实现异步更新(就是让异步更新队列里面的更新器循环去调用updateComponent方法)和重写事件对象event
        8.实现ref
            react.js文件中createRef()方法返回{current:null},在createElement()方法中将config中的ref单独返回,在react-dom.js文件中createDOM()方法中将真实dom挂载ref上     
        9.Context上下文
            在react.js文件中增加createContext()方法返回一个对象包含Provider/Consumer函数组件,Provider提供的数据可以被Consumer消费
        10.React.cloneElement()
            在react.js文件中cloneElement()方法接收一个组件返回一个新组件
        11.Fragment
            在react.js文件中createDOM()方法如果type是reactFragment类型,那么创建一个文档碎片document.createDocumentFragment()即可
        12.PureComponent
            Component.js文件中定义PureComponent类继承Component类,内部实现了shouldComponentUpdate()方法比较了属性和状态,组件状态和属性没有发生改变时,不在重新渲染组件
            在react.js文件中导出PureComponent类     
        13.旧版生命周期函数
            1)在react-dom.js文件中mountClassComponent()方法中创建类实例后调用componentWillMount()方法,产生真实dom后调用componentDidMount()方法
            2)在Component.js文件shouldUpdate()方法如果组件有shouldComponentUpdate()方法并且返回值为true接着调用forceUpdate()往下走
            3)在forceUpdate()方法中调用componentWillUpdate(),更新完后调用componentDidUpdate()方法
            4)diff算法compareTwoVdom()中如果老有新没有那么把老的真实dom移除掉并且调用componentWillUnmount()生命周期函数
            5)diff算法中如果标签是个类组件那么调用updateClassInstance()方法对比类组件,此处调用componentWillReceiveProps()生命周期
        14.新版生命周期函数
            在react-dom.js文件的mountClassComponent()方法调用实例render()方法前调用getDerivedStateFromProps()生命周期,在Component.js文件shouldUpdate()方法shouldComponentUpdate()
            生命周期前调用getDerivedStateFromProps()生命周期,forceUpdate()方法中当componentDidUpdate()生命周期前调用getSnapshotBeforeUpdate()生命周期
6.React Hooks
    让函数组件可以使用state/ref/生命周期函数
    1)useState(使用state,状态改变了才出发组件更新)
        import React,{useState} from 'react';
        function Counter(){
            //状态初始值number为0,setNumber(number+1)可以改变状态
            //setNumber(x=>x+1)可以函数式更新,x就是number,多次触发x是最新状态,非函数式更新,状态是基于初始状态计算的
            //useState()参数可以是个函数,number值是函数返回值
            let [number,setNumber] = useState(0);
            return ()
        }
    2)useRef(使用ref)
        import React,{useRef} from 'react';
        function Counter(){
        //numberRef.current可以获取到标签
        //useRef的参数作为返回对象current的初始值,useRef不但具备了createRef的功能同时可以用来获取最新state状态值
        let numberRef = React.useRef(0);
            return (
                <input type="text" ref={numberRef}/>
            )
        }
    2)forwardRef(让函数组件作为子组件可以使用ref)
        将ref从父组件中转发到子函数组件中的dom元素上,子组件组件接受props和ref作为参数
        import React,{forwardRef} from 'react';
        function Child(props,ref){
          return (
            <input type="text" ref={ref}/>
          )
        }
        //经过forwardRef()包装后Child变成类组件,返回函数组件的调用结果并传递props,ref参数
        Child = forwardRef(Child);
        function Parent(){
          let [number,setNumber] = useState(0); 
          const inputRef = useRef();
          function getFocus(){
            inputRef.current.value = 'focus';
            inputRef.current.focus();
          }
          return (
              <>
                <Child ref={inputRef}/>
                <button onClick={()=>setNumber({number:number+1})}>+</button>
                <button onClick={getFocus}>获得焦点</button>
              </>
          )
        }
    2)useImperativeHandle
        useImperativeHandle可以让你自定义暴露给父组件的ref.current值,useImperativeHandle 应当与 forwardRef 一起使用
        这样可以避免在父级中随意操作子级元素
        import React,{useImperativeHandle} from 'react';
        function FunctionChild(props,ref){
          let inputRef = React.useRef();//return {current:null}
          //useImperativeHandle参数2的返回值会给ref.current,这样父组件中就不能随意操作子组件元素
          useImperativeHandle(ref,()=>(
            {
              focus(){
                inputRef.current.focus();
              }
            }
          ));
          return <input ref={inputRef}/>
        }
        const ForwardFunctionChild = React.forwardRef(FunctionChild);
        function Parent(){
          let [number,setNumber]= React.useState(0);//定义一个状态
          const functionChildRef = React.useRef();//生成一个ref对象 {current:null}
          const getFocus = ()=>{
            functionChildRef.current.focus();
          }
          return (
            <div>
              <ForwardFunctionChild ref={functionChildRef}/>
               <p>{number}</p>
              <button onClick={()=>setNumber(x=>x+1)}>+</button>
              <button onClick={getFocus}>获得焦点</button>
            </div>
          )
        }
    3)memo,useMemo,useCallback(减少子组件渲染次数)
        memo让函数组价具有PureComponent的特征,属性不改变不会触发更新,useMemo/useCallback让父组件重新渲染时,数据随依赖属性不变而不变
        import React,{memo,useMemo,useCallback,useState} from 'react';
        function App(){
          const[name,setName]=useState('zhufeng');
          const[number,setNumber]=useState(0);
          //useMemo和useCallback所依赖的属性number变化时useMemo才会调用参数1返回值给data,useCallback参数1直接给handleClick
          //这样可以让data和handleClick的引用地址没有变,这样memo(Child)组件所接受的属性没有发生变化不会触发更新
          //空数组表示依赖项永远不变,所以回调函数只会执行一次
          let data = useMemo(()=>({number}),[number]);
          let handleClick = useCallback(()=> setNumber(number+1),[number]);
          return (
            <div>
              <input type="text" value={name} onChange={event=>setName(event.target.value)}/>
              <Child data={data} handleClick={handleClick}/>
            </div>
          )
        }
        //孩子只能写成箭头函数
        let  Child = ({data,handleClick})=>{
          console.log('Child render');
          return (
             <button onClick={handleClick}>{data.number}</button>
          )
        }
        Child = memo(Child);
    4)useReducer
        useState的替代方案。useState只是一个简化版的useReducer是一个语法糖,它接收一个形如 (state, action) => newState 的 reducer,
        并返回当前的 state 以及改变state的 dispatch 方法
        import React,{useReducer} from 'react';
        function reducer(state,action){
          switch(action.type){
            case 'ADD':
              return {number:state.number+1};
            default:
              return state;  
          }
        }
        let initialState = 0;
        function init(initialState){
          return {number:initialState};
        }
        function Counter(){
          //init可选参数,不传state就是initialState,传的话state就是把initialState作为init函数参数的返回值
          //dispatch接收一个action类型type去reducer中找到对应的action执行返回一个新state
          let [state,dispatch] = useReducer(reducer,initialState,init);
          return (
            <div>
              <p>number:{state.number}</p>
              <button onClick={()=>dispatch({type:'ADD'})}>number+</button>
            </div>
          )
        }
    5)useContext
        接收一个 context 对象(React.createContext 的返回值)并返回该 context 的当前值(就是Provider组件接收的value值)
        import React,{useContext} from 'react';
        const CounterContext = React.createContext();
        function App(){
            const [state, dispatch] = React.useReducer(reducer, {number:0});
            return (
                <CounterContext.Provider value={{state,dispatch}}>
                  <Counter/>
                </CounterContext.Provider>
            )
        }
        function Counter(){
          //useContext可以拿到Provider组件接收的value值
          let {state,dispatch} = React.useContext(CounterContext);
          return (
              <>
                <p>{state.number}</p>
                <button onClick={() => dispatch({type: 'add'})}>+</button>
                <button onClick={() => dispatch({type: 'minus'})}>-</button>
              </>
          )
        }
    6)useEffect
        useEffect可以让函数组件实现class 组件中的 componentDidMount、componentDidUpdate 和 componentWillUnmount,只不过被合并成了一个 API
        import React,{useEffect} from 'react';
        function FunctionCounter(){
          let [number,setNumber]=useState(0);
          //useEffect里的参数1函数会在第一个挂载之后和每次更新之后和组件卸载后都会执行
          //参数2是依赖项,再次执行useEffect依赖项不变不执行参数1函数
          //参数1函数返回的函数会在下一次执行useEffect里的参数1函数前执行
          useEffect(()=>{
            //document.title = `你点击了${number}次`;
            let timer = setInterval(()=>{
              setNumber(number=>number+1)
            },1000);
            return ()=>{ //清除副作用
              clearInterval(timer);
            }
          },[number]);//空数组表示依赖项永远不变,所以回调函数只会执行一次
          return (
            <div>
              <p>{number}</p>
              <button onClick={()=>setNumber(number+1)}>+</button>
            </div>
          )
        }
    6)useLayoutEffect
        useLayoutEffect和useEffect一样,区别是useEffect会在浏览器渲染结束后执行,useLayoutEffect 则是在 DOM 更新完成后,浏览器绘制之前执行
    7)竞态
        请求更早但返回更晚的情况会错误地覆盖状态值
        import React, { useEffect, useState } from 'react';
        import ReactDOM from 'react-dom';
        const API = {
          async fetchArticle(id){
            return new Promise((resolve)=>{
                setTimeout(()=>{
                  resolve({id,title:`title_${id}`});
                },1000*(5-id));
            });
          }
        }
        function Article({ id }) {
          const [article, setArticle] = useState({});
          useEffect(() => {
            let didCancel = false;
            async function fetchData() {
              const article = await API.fetchArticle(id);
              //通过didCancel变量,如果再次发送请求就取消上次状态的修改
              if (!didCancel) {
                setArticle(article);
              }
            }
            fetchData();
            return () => {
              didCancel = true;
            };
          }, [id]);
          return (
            <div>
              <p>{article.title}</p>
            </div>
          )
        }
        function App(){
          let [id,setId] = useState(1);
          return (
            <div>
              <p>id:{id}</p>
               <Article id={id}/>
               <button onClick={()=>setId(id+1)}>改变id</button>
            </div>
          )
        }
    8)effect回调里读取最新的值
        有时候你可能想在effect的回调函数里读取最新的值而不是捕获的值。最简单的实现方法是使用refs
        import React,{useEffect,useRef,useState} from 'react';
        import ReactDOM from 'react-dom';
        function Counter() {
          const [count, setCount] = useState(0);
          const latestCount = useRef(count);        
          useEffect(() => {
            latestCount.current = count;
            setTimeout(() => {
              console.log(`You clicked ${latestCount.current} times`);
            }, 3000);
          });
          return (
            <div>
              <p>{count}</p>
              <button onClick={()=>setCount(count+1)}>+</button>
            </div>
          )
        }
    9)自定义 Hook
        有时候我们会想要在组件之间重用一些状态逻辑,自定义hook函数的名字必须以 use或大写字母 开头,并且调用了其他的 Hook,则就称其为一个自定义 Hook
        function useNumber(){
          const [number,setNumber] = useState(0);
          useEffect(() => {
             console.log('开启一个新的定时器')
             const $timer = setInterval(()=>{
              setNumber(number+1);
             },1000);
             return ()=>{
              console.log('销毁老的定时器')
                 clearInterval($timer);
             }
          });
          return number;
        }
        function Counter1(){
          let number1 = useNumber();
          return (
              <>
                  <p>{number1}</p>
              </>
          )
        }
7.React Hooks源码
    1)useState
        1.在1.useState.js文件useState()方法中参数是函数,那么调用函数把返回值给老状态lastState,否则直接把参数给老状态lastState
        2.更新状态方法setState()参数是个函数那么把老状态lastState传给函数,函数返回值重新赋值给lastState,最终useState()方法返回一个数组,包含状态和更新状态的方法
    2)useRef
        在1.useState.js文件useRef()方法中返回{current:null}即可
    3)memo,useMemo,useCallback
        1.useCallback.js文件中memo()方法接收一个组件在返回一个组件,这个组件继承PureComponent类,同时把返回组件接收的属性给接收的组件
        2.useState()方法把状态放在hookStates数组中,索引hookIndex++
        3.useMemo()方法初次调用把useMemo参数1函数调用的返回值和依赖变量放在hookStates数组中,索引hookIndex++;再次调用useMemo()方法拿出新老依赖变量进行比较
            没有变化什么都不做,有变化就更新useMemo参数1函数调用的返回值和依赖变量放在hookStates数组中
        4.useCallback()方法和useMemo()方法一样,不同的是参数1函数没有调用
        5.在render()方法中会重置索引hookIndex,这样就能保障每次调用useCallback()/useMemo()方法对比的数据对应的索引一样
    4)useReducer及useState
        1.在3.useReducer.js文件中useReducer()方法把state初始值保存到hookStates数组中,向外返回数组包含元素状态state和改变状态的dispatch方法
        2.dispatch()方法接收一个action,如果有reducer,那么reducer执行的结果作为新state,没有reducer,那么action就是新state
        3.useState()方法其实就是没有传reducer的useReducer()方法
    5)useEffect
        在4.useEffect.js文件中useEffect()方法中第一次调用useEffect()方法执行参数1函数,函数返回值和依赖项存到hookStates数组中
        下次在调用useEffect比较依赖项,依赖项不同调用上一次销毁函数,再调用函数1,再把函数返回值和依赖项存到hookStates数组中
    6)forwardRef
        在7.useImperativeHandle.js文件forwardRef()方法接收一个函数组件返回一个类组件,类组件的返回值是函数组件的返回值,并转入props和ref参数
    7)useImperativeHandle
        在7.useImperativeHandle.js文件useImperativeHandle()方法,让ref.current等于参数2函数的返回值
    8)useLayoutEffect
        在9.useLayoutEffect.js文件中把useEffect()方法的实现逻辑中在定时器中执行回调变成在Promise.resolve().then()或queueMicrotask()中执行,
        这两个都是微任务,微任务都是在浏览器绘制前执行的
    6)总结
        1.hookStates数组是用来存state和依赖deps和依赖所对应的回调
        
8.项目
    1)初始化项目
        1.npm init -y    初始化package.json文件
        2.创建.gitignore文件指定不上传git的文件
        3.编写webpack.config.js文件
            cnpm install react react-dom @types/react @types/react-dom react-router-dom @types/react-router-dom @ant-design/icons antd redux react-redux @types/react-redux redux-thunk  redux-logger @types/redux-logger redux-promise @types/redux-promise connected-react-router  classnames @types/classnames react-transition-group @types/react-transition-group express express-session body-parser cors axios  redux-persist immer redux-immer --save
            cnpm install webpack webpack-cli webpack-dev-server html-webpack-plugin babel-loader typescript @babel/core @babel/preset-env @babel/preset-react @babel/preset-typescript  babel-plugin-import style-loader css-loader postcss-loader less-loader less autoprefixer px2rem-loader  lib-flexible eslint @types/eslint --save-dev
            1)搭建开发环境
                1.配置mode、entry、output、devtool、devServer、resolve、module(ts/jsx、css/less、图片)
            2)生产环境处理
                1.mode、devtool、module(css)
            3)在package.json文件中添加build和serve指令
        4.生成一个tsconfig.json文件来告诉ts-loader如何编译代码TypeScript代码
            tsc --init    (这边删除了制动生成的内容,自己写了)
            {
                //模块编译对象
              "compilerOptions": {
                "moduleResolution":"Node", //使用node风格来解析文件
                "outDir": "./dist", //输出目录
                "sourceMap": true, //是否生成sourceMap文件,便于调试
                "noImplicitAny": true, //不能允许隐藏的any类型
                "module": "ESNext", //生成的代码规范,ESNext新的一种代码规范
                "target": "es5", //生成的js代码是es5
                "jsx": "react", //react模式下jsx语法直接转换成React.createElement()语法,这样babel不需要在经过这个步骤
                "esModuleInterop":true, //支持转换es6模块为commonjs模块,node风格是commonjs规范,es6需要转换
                "baseUrl": ".",  // 解析非相对模块的基地址,默认是当前目录
                "paths": {       // 路径映射,相对于baseUrl
                  "@/*": ["./src/*" ] //把@映射为src目录
                }
              },
              //包含的文件
              "include": [
                "./src/**/*"
              ]
            }
        5.生成eslint配置文件.eslintrc.js或.eslintrc.json,根据你下方的选择会生成对应的文件
            1)npx eslint --init
                按照自己的需求进行选择,依次选择
                How would you like to use ESLint? To check syntax and find problems
                What type of modules does your project use? JavaScript modules (import/export)
                Which framework does your project use? React
                Does your project use TypeScript? Yes
                Where does your code run? Browser
                What format do you want your config file to be in? JavaScript
                Would you like to install them now with npm? Yes
            2)在package.json文件中添加lint指令
                // 在src文件下查找后缀名为.tsx(--ext .tsx)解析,有问题自动修复(fix)
                //执行npm  run lint才会检查修复
                "lint": "eslint --ext .tsx --fix src",
            3)配置保存自动修复eslint错误
                //vscode下载eslint插件
                //src同级目录下创建 .vscode\settings.json文件
                {
                  "eslint.autoFixOnSave": true, // 保存自动修复
                  "eslint.validate": [ // 修复的语言
                      "javascript",
                      "javascriptreact",
                      {
                          "language": "typescript",
                          "autoFix": true
                      },
                       {
                          "language": "typescriptreact",
                          "autoFix": true
                      }
                  ]
                }
        6.在index.html文件中增加js代码完成移动端适配(配合webpack.config.js文件中的px2rem-loader配置)
        7.设置git提交规范
            1)git init
                在每个 Git 项目根目录下,都会有一个隐藏的 .git 目录,其中 hooks 目录中提供了许多默认的钩子脚本,去掉其默认的 .sample 后缀即可在对应的步骤执行该脚本文件
            2)安装husky
                // 安装 husky 之后,可以看到 .git/hooks 目录中文件的变化
                cnpm install husky --save-dev
            3)全局或局部安装commitizen
                //全局安装可以用git cz代替git commit
                //git cz可以通过选择项自动生成合法提交信息,git commit需要手动提交合法信息
                cnpm install -g commitizen
            4)安装cz-conventional-changelog
                //cz-conventional-changelog是一个commitizen的adapter,它实现的是conventional-changelog-angular一套业界公认和常用的convention
                cnpm i cz-conventional-changelog -D
            5)安装commitlint
                //commitlint用来在代码提交前来校验我们的代码是否符合标准
                //commitlint也需要个adapter @commitlint/config-conventional
                cnpm install @commitlint/config-conventional @commitlint/cli  --save-dev
            6)配置package.json
                //可以通过git cz指令生成一条符合规范的提交信息
                //git commit -m "feat: xxx"
                {
                  "config": {
                    "commitizen": {
                      "path": "node_modules/cz-conventional-changelog"
                    }
                  }
                },
                "husky": {
                  "hooks": {
                      // 提交信息的规范是commitlint规范
                    "commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
                  }  
                },
                // @commitlint/config-conventional可以检查commitlint规范
                "commitlint": {
                    "extends": [
                      "@commitlint/config-conventional"
                    ]
                  }
            7)生成变更日志(在CHANGELOG.md文件中有所有提交信息)
                cnpm install -g conventional-changelog-cli
                conventional-changelog -p angular -i CHANGELOG.md -s
            8)代码格式化
                1.cnpm install  lint-staged --save-dev
                2.配置package.json
                    {
                      "husky": {
                        "hooks": {
                            //提交前执行lint-staged命令,检查暂存区(自己改得文件,别人的不检查)所有.tsx文件,自动修复
                            //可以修复就提交,否则报错
                          "pre-commit": "lint-staged"
                        }
                      },
                      "lint-staged": {
                        "*.tsx": "eslint --fix"
                      }
                    }
    2)让ts可以识别别名,在tsconfig.json文件中配置
        "baseUrl": ".",  // 解析非相对模块的基地址,默认是当前目录
        "paths": {       // 路径映射,相对于baseUrl
          "@/*": ["./src/*" ] //把@映射为src目录
        }
    3)实现底部tabs组件
        在components/Tabs文件中实现,可以通过a标签控制NavLink样式
    4)路由系统使用
        1.index.tsx文件中
            import {ConnectedRouter} from 'connected-react-router';
            import history from './store/history';
            <ConnectedRouter history={history}></ConnectedRouter>
        2.store/history.tsx文件中
            import {createHashHistory} from 'history';
            const history = createHashHistory();
            export default history;
        3.store/index.tsx文件中
            import {createStore,applyMiddleware} from 'redux';
            import {createStore,applyMiddleware} from 'redux';
            import logger from 'redux-logger';
            import promise from 'redux-promise';
            import thunk from 'redux-thunk';
            import {routerMiddleware} from 'connected-react-router';
            import history from './history';
            import rootReducer from './reducers';
            const store = applyMiddleware(routerMiddleware(history),thunk,promise,logger)(createStore)(rootReducer);
            export default store;
        4.组件中
            type StateProps = ReturnType<typeof mapStateToProps>;
            type DispatchProps = typeof actions;
            // &和合并的意思,Prop包含PropsWithChildren<RouteComponentProps>、StateProps、DispatchProps属性
            type Prop =  PropsWithChildren<RouteComponentProps> & StateProps & DispatchProps;
            function Home(props:Prop){}
            function mapStateToProps(state:RootState):HomeState{
              return state.home;
            }
            export default connect(
              mapStateToProps,actions
            )(Home);
    5)搭建store
        1.store/index.tsx文件中
            const store = applyMiddleware(routerMiddleware(history),thunk,promise,logger)(createStore)(rootReducer);
            export default store;
        2.reducers/index.tsx文件中
            const rootReducer:Reducer<RootState> = combineReducers<RootState>(reducers);
        3.组件中
            type StateProps = ReturnType<typeof mapStateToProps>;
            type DispatchProps = typeof actions;
            // &和合并的意思,Prop包含PropsWithChildren<RouteComponentProps>、StateProps、DispatchProps属性
            type Prop =  PropsWithChildren<RouteComponentProps> & StateProps & DispatchProps;
            function Home(props:Prop){}
            function mapStateToProps(state:RootState):HomeState{
              return state.home;
            }
            export default connect(
              mapStateToProps,actions
            )(Home);
    6)实现头部组件routes\Home\components\HomeHeader
        1.解决引入图片模块ts中报错问题
            在typings下添加image.d.ts声明文件
        2.解决面包屑筛选项高亮
            自定义data-category属性,点击时触发redux改变currentCategory属性,在结合classnames触发高亮类名(使用了事件委托)
        3.使用react-transition-group库实现面包屑动画
    7)实现个人中心组件routes\Profile
        1.在components\NavBar中实现头部
        2.在routes\Profile中props.validate()方法发送用户登录验证,根据用户登录情况返回不同的内容(用户登录状态放在redux中)
        3.在profile.tsx中写action,注册成功跳到登录页,登录成功在本地储存token跳到个人中心页,退出登录清除本地储存的token跳到登录页
            //接口要这样写,await接收回来的数据才能使用到响应体的data属性,请求拦截器返回的是data
            //axios.post<T>,请求拦截器返回的是response,那么post一个T,await接收回来的数据可以使用response
            export function login<T>(values:LoginPayload){
                return axios.post<T,T>('/user/login',values);
            }
        4.实现注册和登录组件
        5.实现上传组件上传头像功能
    8)实现首页轮播图routes\Home\components\HomeSliders
    
9.路由
    1)React路由原理
        不同的路径渲染不同的组件
        有两种实现方式
        HashRouter:利用hash实现路由切换
        BrowserRouter:实现h5 Api实现路由的切换
    2)HashRouter
        利用hash实现路由切换
        window.addEventListener('hashchange',()=>{
            console.log(window.location.hash);
            let pathname = window.location.hash.slice(1);//把最前面的那个#删除 
            root.innerHTML = pathname;
        });
    3)BrowserRouter
        利用h5 Api实现路由的切换
        HTML5 History API包括2个方法:history.pushState()和history.replaceState(),和1个事件window.onpopstate
        1.pushState
            history.pushState(stateObject, title, url),包括三个参数
            第一个参数用于存储该url对应的状态对象(路由参数),该对象可在onpopstate事件中获取,也可在history对象中获取
            第二个参数是标题,目前浏览器并未实现
            第三个参数则是设定的url
            pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶
        2.replaceState
            该接口与pushState参数相同,含义也相同
            唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url
            需要注意的是replaceState不会改动浏览器历史堆栈的当前指针
        3.onpopstate
            该事件是window的属性
            该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针
            在不改变document的前提下,一旦当前指针改变则会触发onpopstate事件
    4)源码
        1.HashRouter和BrowserRouter作为最外层容器传入不同的history给公共容器Router(react-router/Router)
        2.Router中使用React.createContext将路由属性history、location、match传给渲染的路由,并且在listener方法
        监听到路由地址发生变化时,重新渲染页面
        3.Route中path和路由地址一样就渲染组件,否则不渲染
        4.实现createBrowserHistory(history/createBrowserHistory.js),给history对象增加push等方法,内部是使用浏览器history实现的
        5.实现createHashHistory(history/createHashHistory.js),给history对象增加push等方法,historyStack中维护路由历史记录
        6.实现switch:拿到switch组件的所有子组件,渲染匹配到的子组件即可
        7.实现Redirect:history.push(props.to),跳到to属性指定的路径即可
        8.实现link(react-router-dom/Link.js):使用a标签和history.push(props.to),跳到to属性指定的路径即可
        9.实现二级路由:上面的操作已经可以直接实现二级路由了
        10.受保护路由(components/Protected.js):通过render方法渲染路由,可以给路由加权限
        11.实现NavLink:使用Link来实现,当前to属性和浏览器地址一样的会传递触发样式
        12.实现withRouter:使用高阶组件传递路由属性
        13.实现Prompt:渲染Prompt如果when为true时,给history对象添加message,当调用history路由跳转逻辑时执行message逻辑,Prompt卸载时清空message
            <Prompt // 路由跳转前处理一些逻辑
                when={this.state.isBlocking} // when为true时会执行message逻辑
                message={
                    (location) => `请问你确定离开当前页面,跳转到${location.pathname}吗?`
                }
            />
        14.使用hooks获取路由属性useHistory, useLocation, useParams:去执行上下文上取即可
10.redux
    1)使用
        // reducer改变state,initState state初始值,优先级大于reducer指定的state初始值
        const store = createStore(reducer, initState);
    2)源码
        1.实现createStore(redux/createStore.js):返回getState(获取state值),dispatch(通知reducer更新state和调用订阅事件更新页面),subscribe(订阅重新渲染页面事件,就是调用setState方法)
        2.实现bindActionCreators(redux/bindActionCreators.js):接收actions对象和dispatch方法,返回对象方法逻辑为dispatch(action)
            // 简化组件中使用redux
            import { createStore,bindActionCreators} from '../redux';
            +function add() {
            +    return { type: 'ADD' };
            +}
            +function minus() {
            +    return { type: 'MINUS' };
            +}
            +const actions = { add, minus };
            +const boundActions = bindActionCreators(actions, store.dispatch);
            <button onClick={boundActions.add}>+</button>
        3.实现combineReducers(redux/combineReducers.js):合成之后会返回一个总的reducer,总的reducer里面会包含一个整的state,dispatch时,修改对应reducer的状态,在返回一个总的state
        4.实现react-redux
            //根组件使用
            import { Provider } from './react-redux';
            ReactDOM.render(
              <Provider store={store}>
                <Counter1 />
                <hr />
                <Counter2 />
              </Provider>, document.getElementById('root')
            );
            //子组件内使用
            import actionCreators from '../store/actionCreators/counter2';
            const mapStateToProps = (state) => state.counter2;
            const ConnectedCounter2 = connect(
                mapStateToProps,
                actionCreators
            )(Counter1);
            export default ConnectedCounter2;
            1)react-redux/Provider.js使用上下文向子组件传递store对象
            2)connect使用高阶组件向组件内传递状态和action,订阅事件当状态发生改变时重新渲染页面
            3)函数组件中使用react-redux:去上下文中拿到state和dispatch放回即可
                import { useSelector, useDispatch } from '../react-redux';
                import actionCreators from '../store/actionCreators/counter1';
                function Counter1() {
                    //useSelector 替换是connect mapStateToProps
                    let { number } = useSelector(state => state.counter1);
                    let dispatch = useDispatch();//store.dispatch
                    return (
                        <div>
                            <p>{number}</p>
                            <button onClick={() => dispatch(actionCreators.add1())}>+</button>
                        </div>
                    )
                }
        5.中间件:核心就是重写dispatch方法,让dispatch方法可以处理多种类型的action            
            1)applyMiddleware:创建store,应用中间件,让store的dispatch可以处理多种类型的action    
                import { createStore, applyMiddleware } from './../redux';
                //promise, thunk, logger多个中间件
                const store = applyMiddleware(promise, thunk, logger)
                //createStore创建store
                (createStore)
                //reducer和初始化state
                (combinedReducer, { counter1: { number: 0 }, counter2: { number: 0 } });
            2)实现redux-thunk(action可以返回函数):返回一个新的dispatch方法,方法里面调用老的dispatch方法,dispatch方法可以处理函数和对象action
                thunkAction() {
                    return function (dispatch, getState) {
                        setTimeout(() => {
                            dispatch({ type: actionTypes.ADD1 });
                        }, 2000);
                    }
                }
            3)实现redux-promise(action可以返回promise):返回一个新的dispatch方法,方法里面调用老的dispatch方法,dispatch方法可以处理promise和对象action
                promiseAction() {
                    //promise中间件其实两种方式,第一种是派发一个Promise
                    //直接派发的方式只能处理成功的情况
                    return new Promise((resolve, reject) => {
                        setTimeout(() => {
                            resolve({ type: actionTypes.ADD1 });
                        }, 2000);
                    });
                },
                promise2Action() {
                    return {
                        type: actionTypes.ADD1,
                        //可以处理成功和失败
                        payload: new Promise((resolve, reject) => {
                            setTimeout(() => {
                                let result = Math.floor(Math.random() * 10);
                                console.log('result', result);
                                if (result > 5) {
                                    resolve(result);
                                } else {
                                    reject(result)//如果失败了reducer接收的action会多一个error属性为true
                                }
                            }, 2000);
                        })
                    }
                }
            4)实现redux-logger'(实现打印日志):返回一个新的dispatch方法,方法里面调用老的dispatch方法,并且增加打印日志逻辑
11.connected-react-router库
    将路由属性维护到store中(6.connected文件夹)
    1)history.js文件
        import {createBrowserHistory} from 'history';
        let history = createBrowserHistory();//hash路由使用createHashHistory()
        export default history;
    2)根组件
        import {Provider} from 'react-redux';
        import {ConnectedRouter} from './connected-react-router';
        import store from './store';
        import history from './history';
        ReactDOM.render(
          <Provider store={store}>
              //ConnectedRouter相当于原来的Router,里面多了一些逻辑:路由发生变化时调用history.listen方法将路由属性派发给store
             <ConnectedRouter history={history}>
                <Link to="/">Home</Link>
                <Link to="/counter">Counter</Link>
                <Route exact={true} path="/"  component={Home}/>
                <Route path="/counter"  component={Counter}/>
              </ConnectedRouter>
          </Provider>,
          document.getElementById('root')
    3)store中
        1.index.js文件
            import {createStore,applyMiddleware} from 'redux';
            import reducer from './reducers'
            import {routerMiddleware} from '../connected-react-router';
            import history from '../history';
            //routerMiddleware在action中具备调用history跳转路径的push方法
            let store = applyMiddleware(routerMiddleware(history))(createStore)(reducer);
            export default store;
        2.reducers/index.js文件
            import {combineReducers} from 'redux';
            import {connectRouter} from '../../connected-react-router';
            import history from '../../history';
            let reducer = combineReducers({
                counter,
                router:connectRouter(history)//组件中多了router对象,对象中有action和location属性
            });
            export default reducer;
        3.actions
            export const CALL_HISTORY_METHOD = 'CALL_HISTORY_METHOD';
            //组件中可以直接调用this.props.push(path)跳转路径
            function push(path){
                return {
                    type:CALL_HISTORY_METHOD,
                    payload:path
                }
            }
    4)组件中
        和原来一样,组件中多了router对象,对象中有action和location属性
    5)原理
        1.ConnectedRouter
            返回Router组件,给Router组件传入history属性,里面多了一些逻辑:路由发生变化时调用history.listen方法将路由属性派发给store
        2.connectRouter
            connectRouter(history)返回一个reducer,reducer返回的state包含路由属性
            let initialState = {
                action:history.action,//POP PUSH REPLACE
                location:history.location//{pathname}
            }
        3.push(actions.js文件)
            就是一个action,派发了一个跳转路由的动作
        4.routerMiddleware
            routerMiddleware(history)拦截push方法对应的action,调用history对象对应的方法跳转路由
12.redux-saga
    redux-saga 是一个 redux 的中间件,而中间件的作用是为 redux 提供额外的功能。
    sages 采用 Generator 函数来 yield Effects(包含指令的文本对象)
    Generator 函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
    Effect 是一个简单的对象,该对象包含了一些给 middleware 解释执行的信息。
    你可以通过使用 effects API 如 fork,call,take,put,cancel 等来创建 Effect。
    1)分类 
        root saga 立即启动saga的唯一入口
        watcher saga 监听被dispatch的actions,当接受到action或者知道其被触发时,调用worker执行任务
        worker saga 做左右的工作,如调用API,进行异步请求,获取异步封装结果
    2)用法
        1.store/index.js文件
            import {createStore,applyMiddleware} from 'redux';
            import reducer from './reducer';
            import createSagaMiddleware from '../redux-saga';
            import rootSaga from './rootSaga';
            let sagaMiddleware = createSagaMiddleware();
            let store = applyMiddleware(sagaMiddleware)(createStore)(reducer);
            //run会开始启动saga,让它执行,默认情况下run方法会一直把saga执行结束
            //run必须放在applyMiddleware下面
            sagaMiddleware.run(rootSaga);
            export default store;
        2.store/saga.js文件
            import {takeEvery,put,call,cps,take,all, fork,delay,cancel} from '../redux-saga/effects';
            import * as actionTypes from './action-types';
            // 入口saga,添加任务监听器addWatcher
            function* rootSaga() {
                yield addWatcher();
            }
            //监听saga,监听到组件中派发动作,会调用take通知干活saga add去通知store调用对应的action
            function* addWatcher() {
                const task = yield fork(add);
                console.log(task);
                //take监听某个动作,只监听一次
                yield take(actionTypes.STOP);
            
                yield cancel(task);
            }
            //干活saga,使用put去通知store调用对应的action
            function * add(){
              while(true){
                yield delay(1000);
                //put派发某个动作
                yield put({type:actionTypes.ADD});
              }
            }
            export default rootSaga;
    3)原理
        1.sagaMiddleware和sagaMiddleware.run(rootSaga)
            1)sagaMiddleware给sagaMiddleware.run传channel:订阅(once)和发布(emit)action动作的方法, dispatch:调用emit方法发布action
            2)sagaMiddleware.run方法其实就是generator函数指令管理器,根据对应的effectTypes去执行对应逻辑,页面渲染时会订阅addWatcher里面所有的
            take任务,等到组件中调用dispatch方法时,会调用emit方法执行generator函数的next方法,执行下一步
            3)effectTypes.TAKE使用channel订阅action方法
            4)effectTypes.PUT使用dispatch派发action方法,同时调用channel的emit方法移除订阅的action,这样就实现了take订阅的action只执行一次的效果


            

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值