react

目录

React简介

  • React起源于Facebook 2013年6月发布
  • React是一个用于构建用户界面的JavaScript库
  • React拥有较高的性能,代码逻辑非常简单,越来越多的人已经开始关注和使用它

除去ie8以下版本,其余浏览器都可以很好的支持)

React开发背景

Facebook需要解决的问题:构建数据不断变化的大型应用。

					大量的DOM操作				<==		自动DOM操作 
数据变化
					DOM操作逻辑极其复杂		<==		状态对应内容

虚拟DOM----减少更新次数,减少更新区域

	虚拟dom相当于在js和真实dom中间加了一个缓存。基于React进行开发时所有的DOM构造都是通过虚拟DOM进行,每当数据变化时,React都首先重新构建整个DOM树(减少页面更新次数),然后React将当前整个DOM树和上一次的DOM树进行对比(DOM Diff算法-最小化页面重绘),得到DOM结构的区别,然后仅仅将需要变化的部分进行实际的浏览器DOM更新

为什么虚拟dom会提高性能

1. 虚拟dom相当于在js和真实dom中间加了一个缓存,利用dom diff算法避免了没有必要的dom操作,从而提高性能。
2. 用JavaScript对象结构表示DOM树的结构,然后用这个树构建一个真正的DOM树,插到文档当中当状态变更时,重新构建一颗新的对象树。然后用心的树和旧的树进行对比,记录两棵树的差异把2所记录的差异应用到步骤1所构建的真正的DOM树上,视图就更新了

diff算法的作用

计算出虚拟DOM中真正变化的部分,并值针对该部分进行原生DOM操作,而非重新渲染整个页面

React的diff算法

什么是调和?
	将Virtual(虚拟)DOM树转换成actual(真实)DOM树的最少操作的过程称为调和
什么是React diff算法?
	diff算法是调和的具体实现

React特点

* 声明式设计--React采用声明范式,可以轻松描述应用。(开发者只需要声明显示内容,react就会自动完成)
* 高效--React通过对DOM的模拟,最大限度地减少与DOM的交互
* 灵活--React可以与已知的库或框架很好地配合。
* 组件--通过React构建组件,使得代码更加容易得到复用,能够很好的应用在大项目的开发中。(把页面的功能拆分成小模块-每个小模块就是组件)
* 单向数据流--React是单向数据流,数据主要从父节点传递到子节点(通过props).如果顶层(父级)的某个props改变了,React会重新渲染所有的子节点 

React组件特性

React的核心就是组件:组件的设计目的是提高代码复用率,降低测试难度和代码的复杂程度。
提高代码复用率:组件将数据和逻辑进行封装。
降低测试难度:组件高内聚低耦合(各个元素高集成度低关联性),很容易对单个组件进行测试。
代码的复杂程度:直观的语法,可以极大提高可读性。

React依赖包下载

react核心包 : npm install react --save
react-dom: npm install react-dom --save
babel包 : npm install bable-standalone --save

注意:下载好的文件在node_modules目录。
1.找到react目录,找到这个目录下的umd目录下react.development.js
2.在react-dom/umd目录下找到react-dom.development.js
3.node_modules目录下找到babel-standalone目录下的babel.js

React开发环境搭建

react.js文件是创建React元素和组件的核心文件,react-dom.js文件用来把React组件渲染为DOM,此文件依赖于react.js文件,需在其后被引入。
Babel的主要用途是将ES6转成ES5 同时可以把JSX 语法转换新标准的JavaScript代码让现今浏览器兼容的代码
<script type="text/javascript" src="js/react.min.js"></script>
<script type="text/javascript" src="js/react-dom.min.js"></script>
<script type="text/javascript" src="js/babel.min.js"></script>

React--根DOM节点

页面中需要有一个div容器,容器中的内容都会被React DOM所管理。
这个容器叫做根DOM节点

注意:通常React开发应用时一般只会定义一个根节点

React--JSX

JSX=JavaScript XML就是JavaScript和XML结合的一种格式。是JavaScript的语法扩展。React腿甲你在React中使用JSX来描述用户界面。当遇到<,JSX就当HTML解析,遇到{就当JavaScript解析.

JSX优点

1.JSX执行更快,因为他在编译为JavaScript代码后进行了优化
2.他是类型安全的,在编译过程总就能发现错误
3.使用JSX编写模板更加快速

ReactDOM.render()

ReactDOM.render是React的最基本方法,用于将模板转为HTML语言,并插入页面指定的根DOM节点
<div id="demodiv"></div>
<script type="text/babel">
	//创建虚拟DOM元素对象
	var myDom=<h1>hello React</h1>
	//把虚拟DOM渲染到真实DOM中
	ReactDOM.render(myDom,document.getElementById("demodiv"))
</script>

JSX--注释

注释/*内容*/ hrml标签内注释{/*最外层有花括号*/}
<script type="text/babel">
	var myDom = <div>
  								{/*jsx注释这样玩*/}
  hello world
  						</div>
  ReactDOM。render(myDom,document.getElementById("demodiv"))
</script>
多个 HTML 标签,需要使用一个 父 元素包裹
使用圆括号包裹养成好习惯

<script type="text/babel">
 	var myDom = (
  						<div>
    						<h1>第一行</h1>
    						<h1>第二行</h1>
    				 </div>	
  ) 
ReactDOM。render(myDom,document.getElementById("demodiv"))
 </script>

jsx独立文件使用

React JSX 代码可以放在一个独立文件上创建一个 demoreact.js 文件
页面中引用注意script中type属性
<div id="demo"></div>
<script type="text/babel" src="demoreact.js"></script>

JSX绑定属性

jsx中也可以使用大括号来定义以JavaScript表达式为值得属性:
	<img src={user.url} />
  切记使用了大括号包裹的 JavaScript 表达式时就不要再到外面套引号了。JSX 会将引号当中的内容识别为字符串而不是表达式。

定义样式

定义演示对象,以style属性引用一个对象
样式名以驼峰命名法表示,入text-align需写成textAlign
默认像素单位是px
var _style = {200,textAlign:"center",border:"1px solid #f0f"};
ReactDOM.render(
	<form style={_style}>
		age:<input placeholder="请输入你的年龄"/>
	</form>
	document.getElementById("demo")
);

引用外部样式

引用外部样式时, 不要使用class作为属性名, 因为class是js的保留关键字。JSX 语法上更接近 JavaScript 而不是 HTML,所以 React DOM 使用 className(小驼峰命名)来定义属性的名称,而不使用 HTML 属性名称的命名约定。
React.render(
	<form className="redtxt">
		age:<input placeholder="请输入你的年龄"/>
	</form>
	document.getElementById("demo")
)

jsx规则扩展

  1. html的value属性要写成:defaultValue
  2. html的checked属性要写成:defaultChecked
  3. style 里面写对象
  4. class 写className

React列表渲染

React中使用的map()进行列表的渲染
<script type="text/babel">
 	let arr=[11,22,33,44,55];
	let newhtml=arr.map((item,index)=>{
    	return <p>{index}----{item}</p>
  })
  ReactDOM.render(newhtml,document.getElementById("demo"))
 </script>

React列表渲染--keys

Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此要给数组中的每一个元素赋予一个确定的标识。
一个元素的key最好是这个元素在列表中拥有的一个独一无二的字符串
let arr=[111,222,333,444,555,666,777,888,999];
    let htmlarr=[]
    for(var i=0;i<arr.length;i++){
          htmlarr.push(<li key={i}>{arr[i]}</li>)
    }
  let com=(
      <ul>
          {

               htmlarr   
          }
      </ul>

  )
  ReactDOM.render(com,document.querySelector("#demodiv"))

如何让render重新渲染

<!--不要忘记在每次点击的时候调用控制渲染的函数-->
<div id="demodiv"></div>
<script type="text/babel">
    // 点击变色
    let arr=[
        {name:"xixi1",age:181},
        {name:"xixi2",age:182},
        {name:"xixi3",age:183},
        {name:"xixi4",age:184},
        {name:"xixi5",age:185}
    ]

    let num=-1;
function el(){
  
    return (
        <table border="1">
            <tbody>
                {
                    arr.map((v,i)=>{
                        return (
                            <tr key={i} onClick={()=>{num=i;render(),console.log(num)}} style={{backgroundColor:i==num?'red':''}}>
                                <td>{v.name}</td>    
                                <td>{v.age}</td>    
                            </tr>
                        )
                    })  
                }
            </tbody>    
        </table>
    )
} 
 function render(){
    ReactDOM.render(el(),document.querySelector("#demodiv"))
 }
 render()

对象取值

object.keys()返回一个数组类型 值是方法中对象的键(key)
// 是吧对象转换数组  数组里面的值是 原始对象的key

Object.values()返回一个数组类型 值是方法中对象的值(value)
// 是吧对象转换数组  数组里面的值是 原始对象的val

Object.entries()返回一个数组类型 值是方法中对象的键和值

React遍历对象

<div id="demodiv"></div>
<script type="text/babel">
// 遍历对象
var obj={
    name:"xixi",
    age:18,
    sex:"男",
    love:"女"
}
function el(){
           return Object.values(obj).map((v,i)=>{
                return (
                    <h1 key={i}>{v}</h1>
                )
            })
        }
  ReactDOM.render(el(),document.querySelector("#demodiv"))

模块与模块化

模块:向外提供特定功能的js文件,提高js的复用率简化编写提高循行效率
模块化:当应用的js都是用js模块编写的,这个应用就是模块化应用

组件与组件化

组件:用来实现页面局部功能效果的代码合集(html/css/js)简化复杂页面的编码,提高运行效率
组件化:当应用多使用组件的方式来完成,这个应用就是一个组件化应用

组件的概念

组件是React中非常重要的概念--是可以将UI切分成一些独立的、可复用的部件,这样你就只需专注于构建每一个单独的部件

定义自定义组件--函数组件/无状态组件

组件 首字母大写 首字母大写 首字母大写并且其后每个单词首字母大写
<div id="demodiv"></div>
<script type="text/babel">
//    无状态组件  函数组件 工厂方法组件
function MyCom(){
    return (
        <h1>我是一个组件</h1>
    )
}
function Fu(){
    return(
        <div>
            <MyCom/>
        </div>
    )
}
ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>

创建类组件

需要注意类名的首字母必须大写
一个组件类必须必须必须要实现一个 render 方法。这个方法必须要返回一个jsx元素。必须要用一个外层的jsx元素把所有的内容包裹起来,返回并列的多个元素需要有个父元素包裹
语法:
class MyCom extends React.Component{
              render(){
                  return <div>我是组件</div>
              }
          }

<div id="demodiv"></div>
<script type="text/babel">
//    创建类组件
class MyCom extends React.Component{
    render(){
        return(
            <div>
                <h1>我是类组件</h1>    
            </div>
        )
    }
}   
class Fu extends React.Component{
    render(){
        return (
            <div>
                <MyCom/>    
            </div>
        )
    }
}
ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>

定义父子组件

通过创建多个组件来合成一个组件,即把组件的不同功能点进行分离
<div id="demodiv"></div>
<script type="text/babel">
//    创建类组件
class MyCom extends React.Component{
    render(){
        return(
            <div>
                <h1>我是类组件</h1>    
            </div>
        )
    }
}   
class Fu extends React.Component{
    render(){
        return (
            <div>
                <MyCom/>    
            </div>
        )
    }
}
ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>

props

props 是组件对外的接口。使用props就可以从外部向组件内部进行数据传递 完成父组件传值给子组件
注意:Props对于使用它的组件来说,是只读的。一旦赋值不能修改。也就是说props的值是不可变的只能在渲染的时候传入无法动态赋值。

组件无论是使用无状态组件还是通过 类组件声明,都决不能修改自身的 props。
<div id="demodiv"></div>
<script type="text/babel">
//    props传值 函数组件
let arr=[
    {title:"jdbc1",content:"我是内容哦1"},
    {title:"jdbc2",content:"我是内容哦2"},
    {title:"jdbc3",content:"我是内容哦3"}]
function MyCom(props){
    return(
        <div className="item">
            <span>{props.title}</span> 
            <br/>   
            <span>{props.content}</span>    
        </div>
    )
}
function Fu(){
    let el=arr.map((v,i)=>{
        return (
            <MyCom title={v.title} content={v.content}/>
        )
    })
    return(
        <div className="con">
            {el}
        </div>
    )
}
    ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>


<div id="demodiv"></div>
<script type="text/babel">
//    props传值 函数组件
let arr=[
    {title:"jdbc1",content:"我是内容哦1"},
    {title:"jdbc2",content:"我是内容哦2"},
    {title:"jdbc3",content:"我是内容哦3"},]
function MyCom(props){
    let {title,content}=props.data
    return(
        <div className="item">
            <span>{title}</span> 
            <br/>   
            <span>{content}</span>    
        </div>
    )
}
function Fu(){
    let el=arr.map((v,i)=>{
        return (
            <MyCom data={v}/>
        )
    })
    return(
        <div className="con">
            {el}
        </div>
    )
}
ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>
在参数较多的时候可以把Object类型进行传递
<div id="demodiv"></div>
<script type="text/babel">
//    props传值 函数组件
function MyCom(props){
    return(
        <div className="item">
            <span>{props.name}</span> 
            <br/>   
            <span>{props.age}</span>    
            <br/>   
            <span>{props.sex}</span>    
            <br/>   
            <span>{props.love}</span>    
        </div>
    )
}
function Fu(){
      let obj={
          name:"我是name",
          age:"我是age",
          sex:"我是sex",
          love:"我是love"
      }
          return (
              <div>
                  <MyCom {...obj}/>  
              </div>

          )
  }
    ReactDOM.render(<Fu/>,document.querySelector("#demodiv"))
</script>

逆向传值

import React,{Component}from 'react'
export default class list extends Component{
  constructor(props){
    super(props)
  }
  fun=()=>{
    this.props.myClick("我是传递给父组件的数据")
    //使用props调用父组件传递过来的函数 并传入参数
  }
  render(){
    return(
    	<div>
        <p>我是子组件</p>
        {/* 1.子组件创建事件调用函数*/}
      	<button onClick={this.fun}>点击逆向传值</button>	
      </div>
    )
  }
}
import React,{Component}from 'react'
import List from "./list"
export default class home extends Component{
  constructor(props){
    super(props)
  }
  fun=(val)=>{
    console.log(val)
    //1.父组件创建函数并且定义形参接受传递的数据
  }
  render(){
    return (
    	<div>
      	父组件
        <List myClick={this.fun}/>
        {/*传递给子组件*/}
      </div>
    )
  }
}

同级传值---pubsub-js

下载 npm install --save pubsub-js
在第一个组件中进行数据抛出PubSub.publish("事件名","数据")
import React,{Component}from "react"
import PbuSub from "pubsbu-js"
export default class demoa extends Component{
	fun=()=>{
    //第一个参数是抛出的事件名,第二个是抛出参数
		PubSub.publish("zipao","hello world")
  }
}
render(){
  	return(

    <div>
     	demoa
        <button onClick={this,fun.bind(this)}>点我传递给demob</button>
     </div>

   )
}

在第二组件接受
PubSub.subscribe("监听的时间",(事件,数据)=>{})
import React, { Component } from 'react'
import PubSub from "pubsub-js"
export default class demob extends Component {
    componentDidMount() {
        // 接受zib这个同胞传递过来的数据
        PubSub.subscribe("zibpao",(a,b)=>{
            console.log(`接受的事件名${a}`)
            console.log(`接受的数据${b}`)
        })
    }
    
    render() {
        return (

            <div>
                {/* 我要接受zib这个同胞的传值 */}
                demob
            </div>

       )
  }
}

this.props.children

this.props对象的属性与组件的属性是一一对应的,但是有一个例外就是this.props.children属性,他表示组件的所有子节点

this.props.chilren的值有三种可能:
1、如果当前组件没有子节点,他就是undefined;
2、如果有一个子节点,数据类型是Object;
3、如果有多个子节点,数据类型就是array。

context上下文对象--跨组件传值

创建Index.js文件
import React,{Component,createContext}from 'react'
let context=createContext()
let {Provider,Consumer}=context
class Index extends Component{
	render(){
		return (
      <div>
      	<Provider value={{name:"cwl",age:18}}>
        	{this.props.children}
        </Provider>
      </div>
		)
	}
}
export {Index,Consumer}

在根目录index.js中
import {Index} from "./context/Index.js"
ReactDOM.render(
	<Index>
  	<App/>
  </Index>
)

在需要的组建中进行使用
import React,{Component} from 'react'
import {Consumer} from "../myprovider/index"
class home extends Component{
  render(){
    return(
    	<div>
      	{/*使用context的数据*/}
        <Consumer>
        	{
            (val)=>{
              return(
              	<div>{val.name}---{val.age}</div>
              )
            }
          }
        </Consumer>
      </div>
    )
  }
}
export default home

状态机

在react中开发者只需关心数据。数据改变页面就会发生改变
数据等同于状态,状态改变-页面绑定的数据就由react进行改变
组件被称之为"状态机",视图与状态)——对应
初始化状态    	this.state={}
class	MyCom extends React.Component{
	constructor(props){
		super(props)
		this.state={
			key1:"value1",
			key2:"value2"
		}
	}
	render(){
		return (<div>我是组件</div>)
	}
}

读取状态 this.state.key1
render(){
  //读取状态
  return <div>{this.state.key1}</div>
}
更新状态 this.setState({key1:"newvalue"})
setState()是异步的会自动触发render函数的重新渲染
fun = ()=>{
	//更新state
	this.setState({
		key1:"newval"
	})
}

constructor--super

ES6的继承规则得知,不管子类写不写constructor,在new实例的过程都会给补上constructor。
可以不写constructor,一旦写了constructor,就必须在此函数中写super()super调用父类的构造方法,此时组件才有自己的this,在组件的全局中都可以使用this关键字,
否则如果只是constructor 而不执行 super() 那么以后的this都是错的!!!super()继承父组件的 this

当想在constructor中使用this.props的时候,super需要加入(props),

此时用props也行,用this.props也行,他俩都是一个东西。(不过props可以是任意参数,this.props是固定写法)

为什么不用工厂方式创建组件

再有状态的前提下 不能使用工厂方式创建组件

字符串类型标签解析

dangerouslySetInnerHTML={{__html:this.state.text}}
class App extends React.Component{
  constructor(props){
    super(props)
    this.state={text:"<p>我是一个p标签</p>"}
  }
  render(){
    return(
    	<div dangerouslySetInnerHTML={{__html:this.state.text}}></div>
    )
  }
}

Refs转发

React提供的这个ref属性(不能再无状态组件上使用ref属性,因为他们没有实例)表示为对组件真正实例的引用其实就是ReactDOM.rebder()返回的组件实例

ReactDOM.render()渲染组件是返回的是组件实例;而渲染dom元素时,返回是具体的dom节点

标识组件内部的元素

ref三种用法

  1. 字符串

    class MyCom extends React.Component{
      fun=()=>{
        //字符串获取
        alert(this.refs.elem.value)
      }
      render(){
        return(
        	<div>
          	<input type="text" ref="elem" />
            <button @onClick={this.fun}>点我得到值</button>
          </div>
        )
      }
    }
    
  2. 回调函数

    回调函数就是在dom节点或组件上挂在函数,函数的实参是dom节点,达到的效果与字符串形式是一样的,都是获取其引用
    
    class MyCom extends React.Component{
      constructor(props){
        super(prpos)
      }
      fun=()=>{
        //回调获取
        alert(this.textinput.value)
      }
      render(){
        return(
        	<div>
          	<input type="text" ref={(input)=>{this.textinput=input}} />
            <button @onClick={this.fun}>点我得到值</button>
          </div>
        )
      }
    }
    
  3. React.createRef()

    在React 16.3版本后,使用此方法来创建ref。将其赋值给一个变量,通过ref挂载在dom节点或组件上该ref的current属性将能拿到dom节点或组件的实例
    
    class MyCom extends React.Component{
      constructor(props){
        super(props)
        //创建ref
        this.myRef=React.createRef();
      }
      fun=()=>{
        alert(this.myRef.current.value)
      }
      render(){
        return(
        	<div>
          	<input type="text" ref={this.myRef}/>
            <button @onClick={this.fun}>点我得到值</button>
          </div>	
        )
      }
    }
    

React受控组件

React负责渲染表单的组件。同时仍然控制用户后续输入时所发生的变化。值是来自于state控制的 输入表单元素称为"受控组件"

React组件生命周期

每个类组件都包含"生命周期方法",可以重写这些方法,以便于在运行过程中特定的阶段执行这些方法。

react生命周期分为三种状态:
挂载阶段、更新阶段、卸载阶段

生命周期--挂载阶段

constructor()中完成了React数据的初始化

componentWillMount()
一般用的比较少,它更多的是在服务端渲染时使用。它代表的过程是组件已经经历了constructor()初始化数据后,但是还未渲染DOM时。

componentDidMount()
组件第一次渲染完成,此时dom阶段已经生成,可以在这里调用ajax请求,返回数据setState后组件会重新渲染

componentWillUNmount在组件从DOM中移除之前立刻被调用

ReactDOM.unmountComponentAtNode(document.getElementById("demodiv"));//卸载组件

生命周期--更新阶段

componentWillReceiveProps(nextProps)
在组件接收到一个新的props时被调用

shouldComponentUpdate()
判定组件是否要更新html主要用于性能优化(不分更新)唯一用于控制组件重新渲染的生命周期,由于react中,setState以后,state发生变化,组件会进入重新渲染的流程,在这里return false可以组织组件的更新

componentWillUpdate()
组件即将更新html时候调用shouldComponentUpdate返回true以后,组件进入重新渲染的流程

componentDidUpdate()在组建完成更新后立即调用

render()
函数会插入jsx生成的dom结构,react会生成一份虚拟dom树,在每一次组件更新时,在此react会通过diff算法比较更新前后的恶心就DOm树,比较以后,找到最小的有差异的DOM节点,并重新渲染。

生命周期的方法

componentWillMount 组件渲染之前调用
componentDidMount  组件渲染之后调用在第一次渲染后调用
componentWillReceiveProps在组件接收到一个新的prop时被调用。这个方法在初始化render时不会被调用。
shouldComponentUpdate 判定组件是否要更新html
componentWillUpdate组件即将更新html时候调用
componentDidUpdate 在组件完成更新后立即调用。componentWillUnmount在组件从 DOM 中移除之前立刻被调用。            ReactDOM.unmountComponentAtNode(document.getElementById("demodiv"));//卸载组件

React事件处理

React事件绑定属性的命名采用小驼峰式写法
绑定函数的过程中不加()否则函数会立即执行
<button onClick={this.fun}>点我暂停</button>

React中阻止默认行为使用preventDefault();

事件处理--传递参数

1.通过bind的方式进行传递
<button onClick={this.fun.bind(this,"参数1","参数2","参数n")}>点我</button>
2.通过箭头函数传递。(注意使用箭头函数调用事件对象必须显示的进行传递)
<button onClick={(e)=>{this.fun("参数1","参数2",e)}}>点我</button>

事件处理--修改this指向

  1. 通过bind方法进行原地绑定,从而改变this指向

  2. 通过创建箭头函数

  3. 在constructor中提前对事件进行绑定

  4. 将事件调用的写法改为箭头函数的形式

    class MyCom extends React.Component{
        constructor(props){
            super(props)
            this.state={
                text:"memeda"
            }
        // 提前给函数绑定this
            this.fund=this.fund.bind(this)
        }
        // 方式2:通过创建箭头函数
        fun=()=>{
            this.setState({
                text:"heheda"
            })
        }
       // 方式1:通过bind方法进行原地绑定,从而改变this指向
        funb(){
            this.setState({
                text:"heheda1"
            })
        }
        // 方式4:将事件调用的写法改为箭头函数的形式
        func(){
            this.setState({
                text:"heheda2"
            })
        }
        // 方式3:在constructor中提前对事件进行绑定
        fund(){
            this.setState({
                text:"heheda3"
            })
        }
    
        render(){
            return(
                <div>
                   <h1>this指向</h1>
                   <p>{this.state.text}</p>
                   <button onClick={this.funb.bind(this)}>方式1:通过bind方法进行原地绑定,从而改变this指向</button>
                   <button onClick={this.fun}>方式2:通过创建箭头函数</button>
                   <button onClick={this.fund}>方式3:在constructor中提前对事件进行绑定</button>
                   <button onClick={()=>{this.func()}}>方式4:将事件调用的写法改为箭头函数的形式</button>
                </div>
            )
        }
    }
    

    React条件渲染

    开发中,创建不同的组件来封装各中你需要的行为。然后还可以根据应用的状态变化只渲染其中的一部分。
    
    React中的条件渲染和JavaScript中的一致,使用JavaScript操作符if或条件运算符来创建表示当前状态的元素,然后让React根据他们来跟新UI
    

    条件渲染--if语句

    在React中使用if语句条件渲染是最简单的,但是注意jsx中不允许有if
    
    render(){
      let con
      if(this.state.bool){
        con=<MyComa/>
      }else{
        con=<MyComb/>
      }
      return(
      	<div>
        	<button onClick={this.fun}>点我切换组件</button>
        </div>
      )
    }
    

    条件渲染--三目运算符

    render(){
      return(
        <div>
    			<button onClick={this.fun}>点击</button>
          {this.state.bool?<MyCona/>:</MyConb>}
        </div>
      )
    }
    

    条件渲染-&&

    return(
    	<div>
      	<button onClick={this.fun}>点击</button>
        {this.state.bool==true&&<MyConA/>}
      </div>
    )
    

React脚手架使用

create-react-app安装

npm install -g create-react-app 安装脚手架

create-react-app --version 查看版本
 
create-react-app 项目名  创建项目

cd 项目名 切换到创建好的项目中

npm start 启动运行

切换npm 镜像路径

如果创建项目过慢 可以尝试把npm下载地址切换成淘宝镜像
查看镜像地址 npm get registry
切换淘宝镜像 npm config set registry http://registry.npm.taobao.org/
切换回原始的 npm config set registry http://registry.npmjs.org/

React脚手架使用目录结构

public静态资源文件夹:里面的index.html是整个react项目最终打包的index入口页面的项目模板,但是和我们的代码编写无关,和最终的页面展示是相关的。

静态资源:可以直接在项目中访问
比如pulic文件夹的内容想访问直接在浏览器中输入
http://localhost:3000/logo192.png(文件名)即可查看

src文件夹:使我们编写代码的地方

src/index.js:是整个项目的入口js文件
src/app.js:是被index引入的一个组件,也用一个js表示
src/index.css:index.js中的样式文件
src/app.css:是app.js的样式文件
logo.svg:是一个svg的图片文件。用于界面展示使用

React脚手架使用目录结构:

但是原生的结构很不适合开发所有需要对各市进行修改
创建assets静态资源文件夹
components组件文件夹
pages页面文件夹
styles css样式文件夹

image-20201113192209431

创建组件

ES6 class 来定义一个组件
import React from "react"
class Home extends React.Component{
  render(){
    return(
    	<div>我是组件</div>
    )
  }
}
export default Home

在使用的地方先引用 在使用
import Home from "./pages/home"
<Home/>

多行标签

可以在外层加一个父元素
方式一 传统标签(这种方式会在页面中新增dom节点)
render(){
	return(
		<div>
			<p>内容1</p>
			<p>内容2</p>
		</div>
	)
}
方式二 Fragment不在dom中增加额外节点
			或者使用<></>空标记
import React, { Component } from 'react'
export default class bottomzi extends Component {
    render() {
        return (
            <>
          		<p>111111</p>
          		<p>2222222</p>
          	</>
        )
    }
}
import React, { Component,Fragment } from 'react'
export default class bottomzi extends Component {
    render() {
        return (
            <Fragment>
          		<p>111111</p>
          		<p>2222222</p>
          	</Fragment>
        )
    }
}

使用图片

1.把图片放到public文件夹中 直接使用图片名使用
	<img src="b.jpg"/>
2.不在public文件夹中使用 导入图片路径:
import Imga from "../assets/a.jpg"
<img src={Imga}/>

react脚手架props验证

static propTypes = {
	name:PropTypes.string
}

styled-components

styledcomponents是一个常用的JavaScript里写css类库。和所有同类型的类库一样,通过js赋能解决了原生css所不能具备的能力,比如变量、循环、函数等。

npm install --save styled-components 下载

styled-components基本使用

在需要时用的组建文件夹中穿件styled文件夹并在其中创建js文件
注意组件首字母必须大写不然无法识别
//创建样式
import styled from "styled-components"
export const DemoStyle = styled.div`
	//样式以及子元素样式
color:red;
font-size:100px;
p{
	font-size:20px;
	color:pink;
}
`
//引用并使用
import React,{Compponrnt} from "react"
import {DemoStyle} from "./styleHome.js"
export default class home extends Component{
  render(){
    return(
    	<div>
      	<DemoStyle>
        	我是p标签外面的文字
          <p>我是p标签里面的文字</p>
        </DemoStyle>
      </div>
    )
  }
}

强制刷新forceUpdate()

forceUpdate()就是重新调用render渲染,有些变量不在state上,但你又想这个变量更新的时候,刷新render
export default class demo extends Component{
	constructor(props){
		super(props)
		this.name="cwl"
	}
	fun(name){
		this.name = name
		this.forceUpdate();
	}
	render(){
		return(
			<div>
      	<p>{this.name}</p>
        <button onClik={this.fun.bind{this,"hello world"}}>点我改值并强制刷新</button>
      </div>	
		)
	}
}

react前端配置正向代理跨域

1.找到项目目录下/node_modules/react-script/config/webpackDevServer.config.js
2.然后在其中找到proxy
	proxy:{
		"/api":{
      target:"请求地址",
      changeOrigin:true,
      "pathRewrite":{
				"^/api":"/"
      }
		}
	},
	3.修改api中的地址

路由

根据不同的url来切换对应的组件
实现spa(单页面应用)应用:
整个项目只有一个完整页面
页面切换不会刷新页面

React-Router:提供了一些router的核心API,包括Router,Route,Switch等,但是它没有提供DOM操作进行跳转的API
React-Router-DOM:提供了BrowserRouter,Route,Link等API,我们可以通过DOM的事件控制路由。例如点击一个按钮进行跳转,大多数情况下我们是这种情况,所以在开发过程中,我们更多使用React-Router-DOM。

路由模式-HashRouter和BrowserRouter

HashRouter(hash模式)
URL中会有个#,例如localhost:3000/#,HashRouter就会出现这种情况,他是通过hash值来对路由进行控制。如果你是用HashRouter,你的路由就会默认有这个#。刷新不会丢失数据

BrowserRouter(历史记录模式)
是通过历史记录api进行路由的切换的很多情况下我们则不是这种情况,我们不需要这个#,因为他看起来很乖,这是我们就需要用到BrowserRouter。刷新会丢失404(上线中会出现问题 本地开发中不会)

要切换路由是只需要在index.js设置路由模式即可
import {HashRouter} from "react-router-dom"
ReactDOM.render((<HashRouter><App/>						</HashrRouter>,document.getElementById('root'))

路由-Link与Switch

Link主要API是to,to可以接受string或者一个object,来控制url
NavLink它可以为当前选中的路由设置类名、样式以及回调函数
to属性跳转路径activeClassName当元素处于活动状态是应用于元素的样式

//引用Link
import React from 'react'
import {Route,Link,NavLink} from "react-router-dom"
import Demoa from "./components/demoa"
import Demob from "./components/demob"
function App(){
  return(
  	<div className="App">
    	{/*路由导航*/}
      <Link to="/demoa">去demoa</Link>
      <Link to="/demob">去demob</Link>
      {/*NavLink 可以设置选中状态元素的类名*/}
      <NavLink to="/demoa" acticeClassName="xxx">demoa</NavLink>
      <NavLink to="/demob" acticeClassName="xxx">demob</NavLink>
      {/*路由规则*/}
      <Route path="/demoa" component={Demoa}/>
      <Route path="/demob" component={Demob}/>
    </div>
  )
}
export default App
如果在vscode的终端中启动项目可能会无效 在外部cmd中启动

路由的基本使用

路由最基本的职责就是当页面的访问地址与Route上的path匹配时,就会渲染出对应的UI界面。
1.下载路由模块
	npm install --save react-router-dom
2.在index.js引用路由模块
	import{BrowserRouter}from react-router-dom;
3.在index.js使用路由模式包裹组件
	ReactDOM.render(<BrowserRouter><App/>						</BrowserRouter>,document.getElementById('root'))
4.在app.js中引用路由出口
	import {Router} from "react-router-dom"
5.配置
	<Router path="/路径" component={组件}/>

exact精准匹配

exact代表当前路由oath的路径采用精准匹配,比如说Home的path如果不加上exact,那么path="/about"将会匹配他自己与path="/"这两个,所以一般path="/"这个路由一般不会加上exact,另外需要注意一点的是嵌套路由不要加exact属性,如果父级路由加上,这里例如topics加上该属性,他下面的子路由将不会生效,因为外层强制匹配了。
<Route exact path="/" component={Home}/>
<Route exact path="/about" component={About}/>
<Route exact path="/topics" component={Topics}/>

路由--重定向

为了解决route的唯一渲染,他是为了保证路由只渲染一个路径
<Switch>是唯一的,因为他仅仅只会渲染一个路径,当它匹配完一个路径后,就会停止渲染了

导入Redirect
import {BrowserRouter,Route,Link,NavLink,Redirect} from 'react-router-dom'
定义重定向路径<Redirect from="/" to="/demoa" exact/>
  
  return(
  	<div>
      {/*路由导航*/}
    	<NavLink to="/demoa" activeClassName="xuanzhong">去demoa</NavLink>
    	<NavLink to="/demob" activeClassName="xuanzhong">去demob</NavLink>
      <Switch>
      	<Route path="/demoa" component={Demoa}/>
      	<Route path="/demob" component={Demob}/>
        {/*设置重定向*/}
        <Redirect from="/" to="/demoa" exact/>
        {/*404页面需要卸载最下面 不需要设置path*/}
        <Route component={Democ}/>
      </Switch>
    </div>
  )

二级路由

在子页面中引种路由模块
import  {Route,Link} from 'react-router-dom'
设置相关规则 与路由导航
import React,{Component} from 'react'
import {Route,NavLink} from 'react-router-dom'
import Era from "./era"
import Erb from "./erb"
export default class demoa extends Component{
  render(){
    return(
    	<div>
      	demoa
        <NavLink to="/demoa/era">去era</NavLink>
        <NavLink to="/demoa/erb">去erb</NavLink>
        <Route path="/demoa/era" component={Era}/>
        <Route path="/demoa/erb" component={Erb}/>
      </div>
    )
  }
}

默认选状态在上一级导航中设置to

路由--js跳转

1.push方法在路由页面中跳转this.props.history.push('/dome')

replace()替换当前路径
goBack()后退
goForward()前进

<button onClik={()=>{this.props.history.push('/demoa')}}>push跳转</button>
<button onClik={()=>{this.props.history.goBack()}}>后退</button>
<button onClik={()=>{this.props.history.goForward()}}>前进</button>

路由--withRouter

withRouter作用是让不是理由切换的组件也具有路由切换组件的三个属性(loaction match history)

<button onClik={()=>{this.props.history.push('/demoa')}}>push跳转</button>
会有报错:
	TypeError:Cannot read property 'push' of undefined
解决方式:引用withRouter组件
import {withRouter} from 'react-router-dom'
修改导航组件删除组件的暴露
	export default class bottom extends Component{}
在最底部重新暴露组件
	export default withRouter(Demoa)

路由--params传参

在Router标签后面拼接传递参数的名字
<Route path="/a/:name" component={Demoa}/>
 设置发送的数据
//声明式
	<Link to="/a/我是params传参声明式的数据">a</Link>
//编程式
<button onClick={()=>{this.props.history.push('/a/我是params传参编程式的数据')}}>点我去a</button>

在需要接受的路由组建中接受this.props.match.params.name
componentWillMount(){
  console.log(this.props.match.params.name)
}

优势:刷新地址栏,参数依然存在
缺点:只能传字符串,并且,如果传的值太多的话,url会变得长而丑陋

路由--state传参

在link中设置发送的数据
<Link to={{{pathname:"/demoa"},state:{name:"cwl"}}}>点我去demoa</Link>

在需要接受的路由组件中接受
console.log(thsi.props.location.state.name)

优势:传参优雅,地址栏不显示传递的数据,传递参数可传对象
缺点:刷新地址栏,参数丢失

路由render渲染写法

修改Router里面的组件调用方式为:
render={(props)=>{return <组件/>}}
render调用一个函数那么我们就可以决定什么时候渲染他同时传入props那么就可以在路由组件中国使用histroy:{...},location:{...},match:{...}这几个对象

<Route path="/demoa" render={(props)=>{return <Demo {props}/>}}/>
<Route path="/demob" component={Demo}/>

路由render渲染写法传递数据

只需要向组件内部传递即可 正向传值
<Route path="/demoa" render={(props)=>{return <Demo {...props} text='我是给路由传递的参数'/>}}/>
<Route path="/demob" component={Demo}/>
  
  取值
render(){
  console.log(this.props.text)
  return(<div>我是组件</div>)
}

路由验证

如果想对路由进行验证的话只需要在函数中进行逻辑编写 既可以设置具体渲染那个组件
<Route path="/demoa" render={(props)=>{return false?<Demoa {...props}/>:<Redirect to="/login"/>}}/>
<Route path="/demob" component={Demob}/>

高阶组件HOC

在React组件的构建过程中,常常有这样的场景,有一类功能需要被不同的组件公用。可以复用在react组件中的代码与逻辑
HOC--参数是组建同事返回值也是组件 这类组件我们叫做高阶组件,高阶组件的本质是高阶函数 比如js中的map()

高阶组价是React中用于崇墉组件逻辑的高级技术。HOC本身不是React API的一部分。他们是从React构思本质中浮现出来的一种模式。

例如封装一个功能在很多歌组件中都想使用 就是每个组件都带一个版权信息
import React from "react"
let HOC=(Com)=>{//形参自己定义(首字母大写)
  render(){
    return(
    	<div>
    			来自于:cwl.<Com/>
    	</div>	
    )
  }
}
export default HOC

使用的时候需要修改组件大的导出方式
import React,{Component} from 'react'
import HOC from '../HOC/index'
class Demoa extends Component{
  render(){
    return(
    	<div>
      	我是组件demoa
      </div>
    )
  }
}
export default HOC(Demoa)

高阶组件HOC接受props

如果组件是被高阶组件导出的 那么在正向传值的时候需要在高阶组件中进行传递

import React,{Component} from 'react'
import HOC from '../HOC/index'
class Demoa extends Component{
  render(){
    return(
    	<div>
      	来自于:cwl<Com {...this.props}/>
      </div>
    )
  }
}
export default HOC()

<div>
	我是一个组件--{this.props.text}  
</div>

高阶组件HOC--反向继承

反向继承最核心作用,是渲染劫持(拦截了渲染可以让我们进行条件渲染)。super.render()是调用父类的render()渲染
var Xw=(Com,num)=>{
  return class Demo extends Component{
    render(){
      if(num===1){
        return(
          <div>
          	来自于:cwl<Com {...this.props}/>
          </div>
        )
      }else{
        return(<Com {...this.props}/>)
      }
    }
  }
}

REDUX

Redux是为javascript应用程序提供一个状态管理工具
集中的管理react中多个组件的状态
redux是专门作状态管理的js库(不是react插件库可以用在其他js框架中例如vue,但是基本用在react中)

redux使用场景

某个组件的状态需要共享的时候
某个组件的状态需要在任何地方都可以拿到
一个组件需要改变全局状态
一个组件需要改变另一个组件的状态

redux三大原则

单一数据源:整个英勇的state被存储在一个object tree中,并且这个object tree只存在于唯一一个store中
State是只读的:唯一改变state的方法就是触发action,action是一个用于描述已发生事件的普通对象
使用纯函数来执行修改:为了描述action如何改变state tree,你需要编写reducers(一些纯函数,他接受先前的state和action)

redux常用方法和概念

store:管理着整个应用的状态,可以通过getState()来重新获得最新的状态(state)
action:是唯一可以改变状态(state)的方式,服务器的各种推送、用户自己做的一些操作,最终都会转换成一个个的action,而且这些action就是修改的动作,可以通过dispatch()方法来进行调用
reducer:reducer是一个纯函数,它接受action和当前state作为参数,返回一个新的state.(纯函数就是只要传入相同的参数,每次都应返回相同的结果)

createStore() 	创建一个redux store来存放应用中所有的		state,一个应用智能有一个store。函数返回store对象
getState() 获取数据
dispatch()分发action,这是改变state的唯一方法
subscribe()添加一个变化监听器,每当改变store的时候就会执行

redux使用

npm install --save redux 下载
建议:在项目中创建一个store文件夹用来保存redux相关的内容

在store中创建index.js

import {createStore} from "redux"//1.引用
let data={//5.初始化数据
	text:"我是数据"
}
let reducer=(state=data,action)=>{
	//4、创建reducer 并且传入state数据 action动作
	return state
}
let store =createStore(reducer);
//2、创建store核心对象并且传入reducer
export default store //3、暴露

在src下的index.js中使用
import store from "./store/index.js"
console.log(store.getState().text)

在组件中使用
import React,{Component}from 'react'
import store from "../store/index"//1.引用
export default class demo extends Component{
  constructor(props){
    super(props)
    this.state={
      txt:store.getState().text//读取数据并且赋值给state
    }
  }
  render(){
    return(
    	<div>
      	redux的数据:{this.state.text}
      </div>
    )
  }
} 

redux修改数据

在reducer中添加action任务
let data = {
	text:"我是redux的数据"
}
export default (state=data,action)=>{
  switch(action.type){
    case "ADD":
      return {...state,text:state.text+1}
    default:
      return state
  }
}

dispatch()方法来进行调用修改动作
subscribe()订阅一个变化监听器,每当改变store的时候就会执行
import React,{Component} from "react"
import store from "../store/index.js"
export default class index extends Component{
  constructor(props){
    super(props)
    this.state={
      n:store.getState().text
    }
  }
  componentDidMount(){
    store.subscribe(()=>{
      //当store数据修改时会执行这个回调
      this.setState({
        n:store.getState().text
      })
    })
  }
  fun(){
    store.dispatch({type:"ADD"})//调用任务
  }
  render(){
		return(
 			<div>
      	index--{this.state.n}
        <button onClick={this.fun.bind(this)}>点我修改数据</button>
      </div>   	
    )
  }
}

redux修改数据传递参数

在dispatch中进行参数传递
	upFun=()=>{
		store.dispatch({type:"ADD",num:123})//传递参数
	}
	reducer中使用
	let con={
		text:"我是render的数据"
	}
	export default {state=con,action}=>{
		switch(action,type){
		case "ADD":
			return {...state,text:state.text+action.num}
			default:
				return state
		}
	}

使用actionCreator统一创建action

redux希望使用专门的工厂函数来创建action
创建store来存放actionCreator.js  
封装相关动作
let add=(num)=>{
	return {type:"ADD",num}
}
let del=(num)=>{
  return {type:"DEl",num}
}
export default {
  add,del
}

在组件中引用并且使用
import React,{Component} from "react"
import store from "../store/index"
import action from "./actionCreator.js"
export default class demo extends Conmponent{
  constructor(props)
  super(props)
  this.state={
    txt:store.getState().text
    //当store数据修改时会执行这个回调函数
      store.subscribe(()=>{
        this.setState({
           txt:store.getState().text
        })
      })
	}
  upFun=()=>{
    store.dispatch(action.add(123))
    //调用封装好的动作并且传入需要的参数
  }
  render()=>{
    return(
      <div>
      	index--{this.state.txt}
        <button onClick={this.upFun}>点我修改数据</button>
      </div>
    )
  }
}

redux封装派发动作名(action任务名

在store中创建actionType.js文件
并且创建常亮来存储
export const NUMBER_ADD = "ADD";

在需要使用的地方引用使用
import {ADD} from "./actionType"
let data={
	text:"我是redux"
}
export default (state=data,action)=>{
	Switch(action.type){
	acse ADD:
		return {...state,text:state.text+action.num}
	default:
		return state
	}
}

在actionCreator.js中也要进行修改

redux封装reducer模块

在组件中创建一个reducer.js来存储
把原本在store文件夹中reducer里面的内容放到刚创建的reducer中
import {ADD} from "../store/actionType"
let data={
	name:"cwl",
	age:19
}
let reducer=(state=data,action)=>{
	Switch(action.type){
		case ADD:
			return {...state,name:state.name+"123"}
		default:
			return state
	}
}
export default reducer

redux合并reducer

使用redux提供的把多个reducer合并成一个

//引用需要合并的reducer
import homeReducer from "../views/reducer.js"
//调用合并reducer方法
import {combineReducers} from "redux"
let reducer=combineReducers({//开始合并
  homeReducer
})
export default reducer

修改调用数据
import React,{Component} from "react"
import store from "../store/index"
import fun from "./actionCreator.js"
export default class homeReducer extends Component{
  constructor(props)
  super(props)
  this.state={
    //store.getState().模块名.变量
    txt:store.getState().homeReducer.text
    store.subscribe(()=>{
      this.setState({
        //store.getState().模块名.变量
        txt:store.getState().homeReducer.text
      })
    })
	}
  fun=()=>{
    store.dispatch(fun.fun())
  }
  render(){
    return (
    	<div>
				数据是:{this.state.txt}
        <button onClick={this.fun}>点我修改数据</button>
      </div>
    )
  }
}

react-redux

一个react的插件
专门用来简化react应用中使用redux
npm install --save react-redux 下载

<Provider/>组件:把 store 提供给其子组件
connect 高阶组件:链接  链接react组件和redux(组件状态要从redux中获取

react-redux使用

在index.js中创建Provoider
import React from 'react'
import ReactDOM from 'react-dom'
import './index.css'
import App from './components/home/home.jsx'
import * as serviceWorker from "./serviceWorker"
import {Provider} from 'react-redux'
import store from "./store"
ReactDOM.render(
	<Provider store={store}>使用Provider把state数据进行提供
		<App/>
  <Provider/>,
  document.getElementById('root')
);
serviceWorker.unregister();

在需要使用数据的组件中
import {connect} from "react-redux"
	class home extends Component{
    constructor(props){
      super(props)
      this.state={
        txt:this.props.state.name
      }
    }
  }
export default connect(state=>{state})(home)

修改数据
add=()=>{
  this.props.diospatch({type:"add"})
}

性能优化

我们在更新数据的时候使用setState修改整个数据 那么数据变了 便利的时候所有内容都要被重新渲染。
数量少没有关系 如果遍历出来的数据很多 那么就会严重影响我们的性能

解决方式1 		————类组件中使用
	使用生命周期的shouldComponentUpdate(nextProps,nextstate)判定组件是否要更新
	shouldComponentUpdate(nextProps,nextState){
		//如果值改变了我就渲染 否则就不渲染
		return nextProps.style !== this.props.style
	}
运行之后发现可以减少不必要的渲染 提高性能
解决方式2 纯组件(PureComponent)————类组件中使用
PureComponent是优化react应用程序最重要的方法,组件发生更新时,组件的props和state没有改变,render方法就不会触发 .可以减少不必要的render次数,提高性能。

省去虚拟dom生成和对比的过程,其实就是react自动帮忙做了一个浅比较(它只比较props和state的内存地址,如果内存地址相同,则shouldComponentUpdate生命周期就返回false。)
import React,{Component,PureComponent} from 'react'
export default class home extends PureComponent{
	render(){
		return()
	}
}
解决方式3 React.memo() ————无状态组件中使用
类似于PureComponent,不同于React.memo是函数/无状态组件,React.PureComponent是类组件。memo依然是一种对象的浅比较

import React,{Component} from 'react'
let home = React.memo((props)=>{
	let {style,text,title}=props
	return (
		<div>
    	<input typr="checkbox" checked={style} onChange={ck.bind(this,i)}/>
      <span style={{color:style?'red':''}}>{text}</span>
    </div>
	)
})
export default home

修改react脚手架端口

端口默认是3000
修改路径:项目路径/node_modules/react-scripts/scripts/start.js

改端口 const DEFAULT_PORT = parseInt(process.env.PORT, 10) || 8888(你要修改的端口);

启动react项目

找到项目中的 package.json文件找到 scripts属性:
 "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
修改后
"scripts": {
    "server": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
修改之后启动命令 npm run serve

react脚手架使用confirm报错问题

原因是eslint 屏蔽了这个方法
render(){
	console.log(confirm('aa'))
	return(<div></div>)
}
err:Unexpected use of 'confirm' no-restricted-globals

解决方式 使用//eslint-disable-next-line

//eslint-disable-next-line
console.log(confirm('lkljk'))

react脚手架使用--配置路径别名

npm install react-app-rewired -D 下载
修改启动命令在package.json 中修改scripts为:"start": "react-app-rewired start"
在根目录文件下,新建 “config-overrides.js” 文件配置
const path = require('path');
function resolve(dir) {
    return path.join(__dirname, '.', dir)
}
module.exports = function override(config, env) {
    config.resolve.alias = {
        '@': resolve('src'),
        "com":resolve("src/components")
    }
    return config;  }
重新启动即可

fetch ---es6

XMLHttpRequest的最新替代技术——Fetch API, 它是W3C的正式标准  (xx.json转换成json字符串)

class Ajax extends React.Component{
  constructor(props){
    super(props)
    this.state={
      name:"cwl"
    }
  }
  componentDidMount(){
    fetch("http://localhost:8888/home/test")
    	.then(res=>{res.json()})//转换成json
    	.then((data)=>{
      	console.log(data);
      	this.setState({name:data})
    })
  }
  render(){
    return(
    	<div></div>
    )
  }
}
//发送 get
fetch('http://localhost:8888/get?key=val',{
	method:"get"
})
.then(req=>req.json())
.then((ok)=>{console.log(ok)})
//发送post
fetch("地址",{
	method:"POST",
	headers: {'Content-Type': 'application/x-www-form-urlencoded'},
	body: "key=val&key=val"
})
	.then(req=>req.json())
	.then(function(ok) {console.log(ok)})

fetch VS ajax VS axios

传统 Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。JQuery ajax 是对原生XHR的封装

axios 是一个基于Promise ,本质上也是对原生XHR的封装,只不过它是Promise的实现版本,符合最新的ES规范,

fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。

qs

下载 npm install --save qs
引用 import qs from "qs"
使用 let key =qs.stringify({key:val,key1:val1})

react与typescript

image-20201124163119051

解决方式卸载掉原有的create-react-app

npm uninstall -g create-react-app

重新全局下载create-react-app 

创建项目 create-react-app 项目名 --template typescript
创建项目:npx create-react-app 项目名 --template typescript

在 npm version >= 5.2.0 开始,自动安装了npx。

npx 这条命令会临时安装所依赖的环境。命令完成后原有临时下载的会删掉,不会出现在 全局中。下次再执行,还是会重新临时安装。不用全局安装,不用担心长期的污染。

也就是说 npx 会自动查找当前依赖包中的可执行文件,如果找不到,就会去 PATH 里找。如果依然找不到,就会帮你安装

文件结构

tsconfig.json包含了工程里TypeScript特定的选项。
tslint.json保存了代码检查器,TSLint将要使用的设置。
package.json包含了依赖,还有一些命令的快捷方式,如测试命令,预览命令和发布应用的命令。
public包含了静态资源如HTML页面或图片。除了index.html文件外,其它的文件都可以删除。
src包含了TypeScript和CSS源码。index.tsx是必须的入口文件

组件创建

创建一个tsx后缀名的文件
import React,{Component} from 'react'
export default class home extends Component{
	public render(){
		return (
      <div>
      	<h1>我是一个组件</h1>
      </div>
		)
	}
}
在需要的位置引用使用  但是引用的时候可能会出错
在引用时候不要加后缀名
//在引用时候不要加后缀名
在引用时候不要加后缀名

数据传递

​ 使用传统的方式进行数据传递的时候发现会报错

在进行数据传递的时候要使用 interface接口对类型限制
在子组件中进行限制设定
import React,{omponent} from "react"
interface IProps{//定义接口数据类型
	title:string
}
export default class home extends Component <IProps> {
	public constryctor(props:any){
    super(props)
  }
  public render(){
    return (
    	<div>
      	<h1>我是一个组件{this.props.title}</h1>
      </div>
    )
  }
}

在街口中定义的内容必须全部传递不能只定义传值

interface ICon {//建议接口的首字母使用I来表示
	title:string,
	age:number   //这里定义的数据必须咋父组件进行传值
}

如果不确定会不会有值那么可以再定义接口的时候设置称为可选项

interface ICon{
	title:string,
	age?:number      //使用? 表示可选项
}

直接定义状态会出现问题 必须先定义状态的接口类型

import React ,{ Component} from 'react'
interface IProps{
	title:string,
	age?:number
}
interface IState{
	name:string
}
export default class home extends Component <IProps,IState>{
  public constructor(props:IPoprs){
    super(props)
    this.state={
      name:"我是一个状态"
    }
  }
  public render(){
    return (
    	<div>
      	<h1>我是一个组件{this.props.title}</h1>
        <h1>使用状态{this.state.name}</h1>
      </div>
    )
  }
}
状态修改
fun=()=>{
  this.setState({name:"我被改了"})
}

逆向传值

子组件
import React,{Component} from 'react'
//3.父组件传递过来的props 那么需要定义类型
interface ICon{
	myClick:any
}
export default class list extends Component <ICon>{
  constructor(props:any){
    super(props)
  }
  //2.使用props调用父组件传递过来的函数 并且传入参数
  fun=()=>{
    //父组件传递进来的事件 但是需要在街口上面订定义
    this.props.myClick("我是传递给父组件的数据")
  }
  render (){
    return (
    	<div>
      		我是子组件
        <button onClick={this.fun}>点我逆向传值</button>
        1.子组件创建事件调用函数
      </div>
    )
  }
}

父组件
import React,{Component} from "react"
import List from "./list"
export default class home extends Component {
  constructor(props:any){
    super(props)
  }
  fun=(val:string)=>{
    console.log(val)
    //1.父组件创建函数并且 定义形参接受传递的数据
  }
  render(){
    return (
    	<div>
      	我是一个组件
        2.传递给子组件
        <List myClick={this.fun}/>
      </div>
    )
  }
}

列表

import React,{Component} from "react"
interface IState{
	arr:Array<any>	//定义state类型
}
export default class home extends Component <{},IState>{
  constructor(props:any){
    super(props)
    this.state={
      arr:[]
    }
  }
  componentDidMount() {
    //fetch es6原生请求数据的方法
    fetch("http://api.artgoer.cn:8084/artgoer/api/v1/user/324380/v3/topic/topicHomeByLabel?pageIndex=1&token=b544cd63-6d42-46fe-a96c-3cf96bae3113&topicId=62187")
    .then(res=>res.json())
    .then(ok=>{
      console.log(ok)
      this.setState({
        arr:ok.data.commentList
      })
    })
  }
  render(){
    return (
      <div>
      	我是一个组件
        {
          this.state.arr.length > 0 ? this.state.arr.map((v,i)=>{
            return (
            	<p key={i}>{v.commentTxt}</p>
            )
          }):<p>请稍等</p>
        }
      </div>
    )
  }
}

umiJS

Umi 是蚂蚁金服的底层前端框架 中文可发音为乌米,是可扩展的企业级前端应用框架。Umi 以路由为基础的,同时支持配置式路由和约定式路由,保证路由的功能完备。同时有强大的插件扩展各种功能

umi可以更好的组织路由 放到配置文件中统一管理

什么时候不用umi?
  需要支持 IE 8 或更低版本的浏览器
  需要支持 React 16.8.0 以下的 React
  需要跑在 Node 10 以下的环境中
  有很强的 webpack 自定义需求和主观意愿
  需要选择不同的路由方案
不同

create-react-app 是基于 webpack 的打包层方案,包含 build、dev、lint 等,他在打包层把体验做到了极致,但是不包含路由,不是框架,也不支持配置。如果想基于他修改部分配置,或者希望在打包层之外的事情,就会遇到困难。

yarn和npm命令对比
功能 yarn npm
初始化 yarn init npm init
安装依赖 yarn install 或者 yarn npm intall xxx --save
新增依赖 yarn add xxx npm intall xxx --save
删除依赖 yarn remove xxx npm uninstall xxx --save
全局安装依赖 yarn global add xxx npm install xxx -g
同时下载多个 yarn add xxx xxx npm install --save xxx xxx

yarn更换镜像源

  • yarn config set registry https://registry.npm.taobao.org

  • umi下载安装

    全局安装	npm install -g umi / yarn global add umi 
    查看版本 umi -v
    安装项目 npm或yarn create @umijs/umi-app
    安装依赖 cnpm install 或yarn
    启动项目 npm/yarn start
    运行http://localhost:8000
    
  • 目录结构

    .umirc.ts配置文件,包含umi内置功能和插件的配置
    .env 环境变量
    dist目录 执行umi build 后,产物默认会存放在这里
    mock目录 存储mock 文件,此目录下所有js和ts文件会被解析为mock文件
    public目录 此目录下所有文件会被copy到输出路径
    /src/.umi	临时文件目录,比如入口文件、路由等,都会被临时生成到这里 目录
    /src/layouts/index.tsx 约定式路由时的全局布局文件
    /src/pages目录 所有路由组件存放在这里
    /src/app.ts 运行时配置文件,可以在这里扩展运动时的能力,比如修改路由、修改render方法等
    
  • HTML模板

    修改默认模板
    新建src/pages/document.ejs,umi约定这个文件存在,会作为默认模板
    
    作用:如果要引用第三方js/css文件 可以直接在HTMl模板进行配置。有很多第三方的插件都没有react版本的 所以采用import的发放时是没有办法进行引用的 也就只能采取上面的方式进行引用
    
  • 页面创建

    umi g命令创建页面或组件
    在项目根路径下执行
    
    umi g page xxx (页面名) 或umi g page xxx/xxx
    umi g page xxx --typescript --less 创建ts页面与less
    
    也可以自己创建react类组件
    
  • 路由

    在 Umi 中,应用都是单页应用,页面地址的跳转都是在浏览器端完成的,不会重新请求服务端获取 html,html 只在应用初始化时加载一次。所有页面由不同的组件构成,页面的切换其实就是不同组件的切换,你只需要在配置中把不同的路由路径和对应的组件关联上。

    配置路由

    ​ 在配置文件umirc.ts中通过routes进行配置,格式为路由信息的数组。

    export default defineConfig({
    	nodeModulesTransform:{
    		type:'none',
    	},
    	router:[
    		{exact:true,path:'/',component:'@/pages/index'},
    	],
    });
    path:路径
    component:组件路径 @代表src路径
    exact: 路径精准匹配
    

    title:当前页面标题

    import {defineConfig} from 'umi';
    export default defineConfig({
    	nodeModuulesTranform:{
    		type:'none'
    	},
    	router:[
    		{path:"/",component:"@/pages/index",title:"我是标题"},
    	],
    });
    

页面跳转

在umi里,页面之间条状有两种方式:声明式和命令式

声明式:通过Link使用,通常作为React组件事件。

import {Link} from 'umi';
export default ()=>{
  <Link to="/home">home</Link>
}

命令式:通过history使用,通常在事件处理中被调用。

import {history} from 'umi';
function ooo(){
	history.push("/home");
}

二级路由

routes:配置路由的子路由

然后在 一级路由中 通过props.children 渲染子路由

export default defineConfig({
	nodeModulesTransform:{
		type:'none'
	},
  routes:[
    {path:'/',component:'@/pages/index',title:'我是标题'},
    {path:'/home',component:'@/pages/home'},
    {
  		path:"/homea",
  		component:'@/pages/phone',
  		routes:[
        {path:"/homep/era",component:"@/pages/era"},
        {path:"/homep/erb",component:"@/pages/erb"},
      ]
		},
  ],
});

约定式路由

  • 除配置式路由外,Umi 也支持约定式路由。约定式路由也叫文件路由,就是不需要手写配置,文件系统即路由,通过目录和文件及其命名分析出路由配置。

  • 如果没有 routes 配置,Umi 会进入约定式路由模式,然后分析 src/pages 目录拿到路由配置。

  • 但是实际上你写了文件之后,手动在浏览器地址栏输入路由没有发生跳转 原因: 使用脚手架搭建的项目会在配置文件中对路由进行配置。

  • 解决方式:删除.umirc.ts路由配置文件

约定式路由---动态路由

  • 约定[]包裹的文件或文件夹为动态路由

    image-20201125153743961

  • 接受

    import React,{useEffect} from 'react'
    export default (props:any)=>{
        useEffect(()=>{
            console.log(props.match.params.id)
        })
        return (
            <div>
            	我是接受动态路由的
            </div>
        )
    }
    

约定式路由---二级路由

  • Umi 里约定目录下有 _layout.tsx 时会生成嵌套路由,以 _layout.tsx 为该目录的 layout。layout 文件需要返回一个 React 组件,并通过 **props.children **渲染子组件。

image-20201125154220866

  • 必须在父路由中加入this.props.children来渲染子路由

    <div>
    	user
    	{this.props.children}
    </div>
    

模拟数据json-server

我们在开发中并不想使用简单的静态数据,而是希望自己起一个本地模拟请求以及请求回来的过程。json-server就是用来完成模拟数据的

下载:npm install json-server -g
查看版本: json-server --version
创建数据

在项目下 创建一个mock的文件夹并且写入相关的数据.json 与src同级

启动
json-server默认端口为3000 我们不能直接启动会和react脚手架冲突 所以我们启动的时候需要修改端口

1.cd 到mock文件夹路径下 
2.json-server --watch json的名字 --port 4000
3.在浏览器中测试一下 http://localhost:4000/数据的key
模拟数据请求
componentDidMount(){
  this.ajaxData()
}
ajaxData=()=>{
  axios.get("http://localhost:8888/home").then(ok=>{
    this,setState({
      arr:ok.data
    })
  })
}
模拟数据发送
尝试在代码中进行发送数据--数据会存储在json文件中
add=()=>{
	axios.post("http://localhost:8888/home",
						{"name":"我是新数据"},
						{'headers':{"Content-type":"application/json"}}
	).then(()=>{
		//重新调用下数据获取的那个函数否则数据添加到json中但是页面不会刷新
		this.ajaxData()
	})
}

注意 用来存放模拟数据的json文件必须要有一个id 否则添加可能会失败,修改json必须重启json-server服务
{
  "arr":[
    {
      "id":1,
      "name":"cwl"
    },
    {
      "id":2,
      "name":"cwl2"
    }
  ]
}

不可变对象immutable

immutable介绍

  • Facebook 工程师使用3年时间打造,与React同期出现,但是没有被默认放到React工具集中,它内部实现了一套完整的数据持久化 里面有很多常见的数据类型Collection List Map Set等

  • 它里面有三种重要的数据结构:

  • Map:键值对集合,对应于Object ES6中也有专门的Map对象

  • List:有序可以重复的列表,对应于Array

  • set:无序且不可重复key的数组

  • immutable Data就是一旦创建就不能再被改变的数据,对于immutable对象的任何修改或添加删除操作都会返回一个新的immutable对象

  • 为什么每次都要返回一个新的immutable对象呢?----- 因为redux中数据是只读的,如果任意一个使用的位置都可以直接修改redux中的数据,那么可能会影响到其它位置引用的内容,造成显示错误

immutable原理

  • immutable实现的原理是(持久化数据结构),也就是使用旧数据创建新数据时,要保证旧数据同时可用且不变。同时为了避免深拷贝把所有节点都赋值一遍带来的性能损耗,immutable使用了(结构共享),即如果对象树中一个节点发生变化,只修改这个节点和受它影响的父节点,其它节点则进行共享

immutable修改

let {Map} = require('immutable')
let a=Map({
    name:'xixi',
    love:Map({
        lovename:'haha',
        age:18
    })
})
let b=a;//创建一个新的变量把Map赋值给b
console.log(b===a);//true 当没有修改时是把地址直接赋值给b

let c=a.set('name','wuwu');//修改必须通过set来进行
console.log(c===a);//false 当修改时会创建一个新的immutable对象
//读取必须使用get
console.log('a.name' + a.get('name') + '----c.name' + c.get("name"))

immutable---Map

  • Map:键值对集合

  • 创建/读取map集合

    let {Map} = require('immutable')
    //创建
    let a=Map({
        name:'xixi'
    })
    //读取
    console.log(a.get('name'))
    
  • 修改

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi'
    })
    let b=a.set('name','haha')//修改会返回一个新的immutable对象
    console.log(b.get('name'))
    
  • 删除map集合

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi'
    })
    let b=a.delete('name')//删除会返回一个新的immutable对象
    console.log(b.get('name'))
    
  • 批量删除map集合

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi',
        age:18,
        sex:'男'
    })
    let b=a.deleteAll(['name','age'])//删除会返回一个新的immutable对象
    console.log(b.get('sex'))
    
  • 清除并返回新的map

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi',
        age:18
    })
    let b=a.clear()
    console.log(b)
    
  • 合并并返回新的map

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi',
        age:18
    })
    let b=Map({
        sex:'男',
        city:'陕西西安'
    })
    let c=a.merge(b)//合并并返回新的map
    console.log(c)
    
  • toJS把Map转换成原生的Object,深转换(无论有多少层都会转换)

  • toJSON或toObject把Map转换成原生Object,浅转换(只转换第一层)

    let {Map}=require('immutable')
    let a=Map({
        b:Map({
            c:Map({
                d:'xixi'
            })
        })
    })
    //深转换
    let demoa=a.toJS()
    console.log(demoa)
    //浅转换
    let demob=a.toJSON()
    consoel.log(demob)
    let democ=a.toObject()
    console.log(democ)
    

    image-20201125142003797

  • toArray转换成数组(浅转换)

    let {Map}=require('immutable')
    let a=Map({
        name:'xixi',
        age:18
    })
    let demoa=a.toArray()
    console.log(demoa)
    

immutable---List

  • List:有序可以重复的列表,对应于Array

  • 创建List两种方式

    let {List}=require('immutable')
    let listArr=List(['a','b','c'])
    console.log(listArr)
    
    //List.of()创建List
    let listArrb=List.of(1,2,3,4)//不用[]
    console.log(listArrb)
    
  • size获取list长度

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    console.log(listArr.size)
    
  • set(下标,值)用于设置指定下标的值

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let la=listArr.set(1,'我被修改了')//每次修改都会返回一个新的list
    console.log(la)
    let lb=listArr.set(10,'我的下标大于当前数组长度最大值')//可以大于数组长度
    console.log(lb)
    let lc=listArr.set(-1,'负数是从右往左')
    console.log(lc)
    
  • delect(下标)删除指定下标

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let a=listArr.delect(1)
    console.log(a)
    let b=listArr.delect(-1)//负数从右往左数
    console.log(b)
    
  • insert()用来更新指定下标的值

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let a=listArr.insert(1,'我被修改了')
    console.log(a)
    let b=listArr.insert(-0,'我被修改了')
    console.log(b)
    
  • update(下标,回调函数)用于更新指定下标的值

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let a=listArr.update(1,x=x+'更新了')
    console.log(a)
    
  • clear()清空并返回一个空list

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let a=listArr.clear()
    console.log(a)
    
  • push、pop、unshift、shift和数组方法功能相同,请自行尝试

  • setSize()重新设置数组长度,小于原始list会被截取,大于会用undefined填充

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let a=listArr.setSize(2)
    console.log(a)
    let b=listArr.setSize(10)
    console.log(b)
    
  • concat()把多个list拼接成一个list,merge()是concat()别名

    let {List}=require('immutable')
    let listArr=List([1,2,3,4])
    let listArrb=List(["aa",'bb'])
    let a=listArr.concat(listArrb)
    console.log(a)
    //merge是concat的别名
    let b=listArr.merge(listArrb)
    console.log(b)
    

immutable在react中使用

immutable-react

  • 简易购物车

import React,{Component} from 'react'
import List from '../components/list.jsx'
export default class home extends Component{
    constructor(props){
        super(props)
        this.state={
            list:[]
        }
    }
    add=()=>{
        let inputname=this.inputname.value
        let inputnum=this.inputnum.value
        this.state.list[inputname]=inputnum
        this.setState({
            list:this.state.list
        })
    }
    render(){
        return (
            <div>
            	<input type='text' ref={(xiaoming)=>{this.inputname=xiaoming}}/>
                <input type='text' ref={(xiaohong)=>{this.inputnum=xiaohong}}/>
                <button onClick={this.add}>添加到购物车</button>
                <br/>
                <List demolist={this.state.list}/>
            </div>
        )
    }
}
  • 编写子组件用来展示数据

import React,{Component} from 'react'
export default class list extends Component{
    constructor(props){
        super(props)
        this.state={
            
        }
    }
    render(){
        let newlist=this.props.demolist//得到props数据
        return (
            <div>
            	{
                    Object.keys(newlist).map((v,i)=>{
                        return (
                            <p key={v}>商品名:{newlist[v]}</p>
                        )
                    })
                }
            </div>
        )
    }
}
  • 使用immutable改造

import React,{Component} from 'react'
import List from '../components/list.jsx'
import {Map} from 'immutable'
export default class home extends Component{
    constructor(props){
        super(props)
        this.state={
            list:Map({})
        }
    }
    add=()=>{
        let inputname=this.inputname.value
        let inputnum=this.inputnum.value
        //immutable对象修改会返回一个新的immutable对象
        let immlist=this.state.list.set(inputname,inputnum)
        this.setState({
            list:immlist
        })
    }
}
import React,{Component} from 'react'
export default class list extends Component{
    constructor(props){
        super(props)
        this.state={}
    }
    render(){
        let elarr=[]
        this.props.demolist.forEach((v,i)=>{
            elarr.push(
                <p key={i}>商品名:{v}---数量:{i}</p>
            )
        })
        return(
            <div>{elarr}</div>
        )
    }
}

immutable在redux中使用

redux-immutable

  • 安装:npm install redux-immutable --save

  • 创建文件夹与文件来创建redux-immuable

  • 引用redux-immutable并使用combineReducers进行合并

    import {createStore} from 'redux'
    import {combineReducers} from 'reudx-immutable'
    let {Map}=require('immutable')
    //state中的数据必须是一个immutable
    let indata=Map({
        name:'xixi',
        age:18
    })
    let data=(state=indata,action)=>{
        return state
    }
    //combineReducers里的对象合并成是一个immutable对象
    let reducers=combineReducers({
        data
    })
    let store=createStore(reducers)
    export default store
    
  • 在根组件传递store

    import React from 'react';
    import ReactDOM from 'react-dom';
    import './index.css';
    import App from './views/home';
    import reportWebVitals from './reportWebVitals';
    import {Provider} from 'react-redux'
    import store from './store'
    ReactDOM.render(
      <Provider store={store}>
        <App />
      </Provider>,
      document.getElementById('root')
    );
    // If you want to start measuring performance in your app, pass a function
    // to log results (for example: reportWebVitals(console.log))
    // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
    reportWebVitals();
    
  • 在需要的组件中读取

    import React,{Comoponent} from 'react'
    import {connect} from 'react-redux'
    class home extends Component{
        render(){
            return (
                <div>
                	home
                    {this.props.state.get('name')}
                </div>
            )
        }
    }
    export default connect((state)=>{
        return {state:state.get('data')}
    })(home)
    

redux-immutable修改

  • 通过dispatch调用修改actions

    class home extends Component{
        add=()=>{
            this.props.dispatch({
                type:'NUM_ADD',
                payload:1
            })
        }
        render(){
            return (
                <div>
                	home==={this.props.state.get('age')}
                    <button onClick={this.add}>点我修改</button>
                </div>
            )
        }
    }
    
  • 创建修改操作actions

    let data=(state=indata,action)=>{
        switch (action.type){
            case "NUM_ADD":
                console.log(state.get('age'))
                console.log(action.payload)
                return state.set('age',state.get('age') + action.payload)
                break;
            default:
                return state
                break;
        }
    }
    

dva

  • React本身只是一个DOM的抽象层,使用组件构建虚拟DOM

  • 如果开发大应用,还需要解决一个问题。

  • 通信:组件之间如何通信?

  • 数据流:数据如何和视图串联起来?路由和数据如何绑定?如何编写异步逻辑?等等

通信问题

  • 组件会发生三种通信

  • 向子组件发消息

  • 向父组件发消息

  • 向其他组件发消息

  • React只提供了一种通信手段:传参。对于大应用很不方便

dva是什么

  • dva是体验技术部开发的React应用框架,将上面三个React工具库包装在一起,简化了API,让开发React应用更加方便和快捷

  • dva = React-Router + Redux + Redux-saga

  • dva名字来自于守望先锋的D.Va(是守望先锋中的一个英雄)

安装dva-cli

  • 全局安装dva-cli:npm install dva-cli -g

  • 查看版本:dva -v

  • 创建新应用:dva new 项目名

  • 启动命令:npm start

定义路由

  • 路由页面创建在dva提供的router文件夹下创建

  • 添加路由信息到路由表,编辑router.js

    import Products from './routes/Products';
    <Route path="/products" exact component={Products} />
    

定义路由导航

  • 在dva中使用路由导航要从dva/router中进行引用

  • 声明式:

    import React,{Component} from 'react'
    import {Link} from 'dva/router'
    export default class topbar extends Component{
        render(){
            return (
                <div>
                    <Link to='/'>home</Link>
                    <Link to='/shop'>shop</Link>
                    <Link to='/user'>user</Link>
                </div>
            )
        }
    }
    
  • 编程式:

    import React,{Component} from 'react'
    import {Link,withRouter} from 'dva/router'
    class topbar extends Component{
        fun=()=>{
            this.props.history.push('/shop')
        }
        render(){
            return (
                <div>
                    <Link to='/'>home</Link>
                    <Link to='/shop'>shop</Link>
                    <Link to='/user'>user</Link>
                    <hr/>
                    <button onClick={this.fun}>点我去shop</button>
                </div>
            )
        }
    }
    export default withRouter(topbar)
    

定义路由模式

  • 切换HashHistory为BrowserHistory

  • 需要下载history依赖:npm install --save history

  • 然后修改入口文件(即src下的index.js文件):

    import { createBrowserHistory as createHistory } from 'history';
    const app = dva({
      history: createHistory(),
    });
    

定义组件

  • 组件在components下进行定义

  • 在需要的地方引用使用

  • 定义组件的方式可以定义无状态组件,也可以是class类组件

  • 无状态组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,精简至只有render方法,大大的增强了编写一个组件的便利

无状态组件的特点

  • 组件不会被实例化,整体渲染性能得到提升

  • 因为组件被精简成一个render方法的函数实现的,无状态就不会再有组件实例化的过程,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升

  • 组件不能访问this对象

  • 无状态组价由于没有实例化过程,所以无法访问组件this中的对象,若想访问this就不能使用这种形式创建组件

  • 组件无法访问生命周期的方法

定义Model

  • model用来处理数据和逻辑,可以把数据和交互都放到model中方便使用

  • 在model文件夹中创建model文件

    export default {
        namespace:"xiaoming",//命名空间名字,必填
        //state就是用来放初始值的
        state:{
    		name:'xixi',
            age:18
        }
    };
    

引用Model

  • 在全局文件index.js中进行引用

    //设置引用
    app.model(require('./model/文件名').default);
    

使用Model

  • 在需要使用的组件中使用connect进行连接

    import React,{Component} from 'react'
    import {connect} from 'dva'
    class home extends Component{
        render(){
            return (
                <div>
                	home
                </div>
            )
        }
    }
    export default connect()(home)
    

读取Model数据

  • 定义方法读取数据

    import React,{Component} from 'react'
    import {connect} from 'dva'
    class home extends Component{
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    

dva-reducers

修改---Model数据

  • 在model文件中定义reducers属性来进行修改操作

    export default {
        namespace:'homemodel',
        state:{
    		name:'xixi',
            age:18
        },
        //修改操作
        reducers:{
       		updata(state,payload){
                console.log('我是修改操作')
                return state //必须要有返回值
            }
    	}
    }
    
  • 在组件中使用dispatch调用reudcer修改操作

    import React,{Component} from 'react'
    import {connect} from 'dva'
    class home extends Component{
        up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata'
            })
        }
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}
                    <button onClick={this.up}>点我进行修改</button>
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    
  • 开始在reducers中进行修改

  • 传递修改数据

    up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata',
                //传递修改数据
                data:{
                    text:'我是要修改的数据'
                }
            })
        }
    
  • reducers中接收数据并修改

    export default {
        namespace:'homemodel',
        state:{
    		name:'xixi',
            age:18
        },
        //修改操作
        reducers:{
       		updata(state,payload){
                console.log('我是修改操作')
                return {...state,name:payload.data.text} //必须要有返回值
            }
    	}
    }
    

dva-effects

知识点扩展---ES6 Generator

  • Generator主要是异步编程,用来封装一个异步任务,是一个异步任务的容器

  • 特点:交出普通函数的执行权(可以让函数在调用时按照我们的需要执行或暂停)

  • 普通函数:在调用时函数中的内容会全部执行

    //普通函数
    function fun(){
        console.log(1);
        console.log(2);
        console.log(3);
    }
    fun()//调用时会全部执行
    
  • generator函数可以 让函数体内的内容随着我们的需要走走停停

  • 在声明函数的function关键字和函数名之间有一个*号(用于区别普通函数)

  • yield(是异步不同阶段的分割线)在generator函数体内进行使用,可以定义不同的内部状态

  • 使用next()来执行generator函数

  • generator函数:

    function *fun(){
        console.log(1);
        yield
        consoel.log(2);
        yield
        consoel.log(2);
    }
    fun()//调用时函数内容不会执行
    
  • 运行后发现函数不会执行

  • 调用generator函数时会返回一个generator对象

  • 使用next()方法来执行generator函数

    function *fun(){
        console.log(1);
        yield 
        console.log(2);
        yield 
        console.log(2);
    }
    let genfun=fun()//会返回一个generator对象
    //每次next()就会执行yield后面的内容
    genfun.next()
    genfun.next()
    genfun.next()
    

知识点扩展---ES6 Generator传参

  • 可以在next()方法中传递参数,可以把值传递到函数中对应的yield中,第一次不会传递

    function *fun(){
        console.log(1);
        yield 
        console.log(2);
        yield 
        console.log(2);
    }
    let genfun=fun()//会返回一个generator对象
    //每次next()就会执行yield后面的内容
    genfun.next(10)//第一次不会进行传递
    genfun.next(20)
    genfun.next(30)
    

dva---异步操作

  • effect在dva框架下就是用来处理异步操作的

  • 在model文件中写入effects属性

    export default {
        namespace:'homemodel',
        state:{
    		name:'xixi',
            age:18
        },
        //修改操作
        reducers:{
       		updata(state,payload){
                console.log('我是修改操作')
                return {...state,name:payload.data.text} //必须要有返回值
            }
    	},
        effects:{
            //使用Generator语法
    		*genup({payload},{put,call}){
                yield console.log('我被执行了')
            }
        }
    }
    
  • 在组件内进行调用,触发方式也是dispatch

    import React,{Component} from 'react'
    import {connect} from 'dva'
    class home extends Component{
        up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata'
            })
        }
        yibu=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/effects名字"
                type:'homemodel/genup'
            })
        }
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}
                    <button onClick={this.up}>点我进行修改</button>
                     <button onClick={this.yibu}>点我调用异步</button>
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    
  • 异步修改数据需要通过effects方法中的put去调用reducers进行修改

  • type对应的是调用reducers的任务名

  • data是传递给reducers的数据

    effects:{
            //使用Generator语法
    		*genup({payload},{put,call}){
                yield put({
                    //type:'reducers名字'
                    type:'updata',
                    data:{
                        text:'我是effects传递的数据'
                    }
                })
            }
        }
    

dva-基本异步请求

dva---异步请求

  • 异步请求在services文件夹下

  • 新建文件封装我们自己的请求

    //可以参考example.js创建我们自己的请求
    import request from '../utils/request'
    export function query(){
        return request('请求地址');
    }
    
  • 传入请求地址中国天气网测试请求地址(需要解决跨域)http://www.weather.com.cn/data/cityinfo/101320101.html

    import request from '../utils/request'
    export function query(){
        return request('http://www.weather.com.cn/data/cityinfo/101320101.html');
    }
    
  • 在组件内调用封装的请求

  • 会出现跨域问题

    import React,{Component} from 'react'
    import {connect} from 'dva'
    //* as 自己起的名字  相当于把当前模块下所有内容引用
    import * as homeapi from '../services/homeapi.js'
    class home extends Component{
        componentDidMount(){
            homeapi.query().then(ok=>{
    			console.log(ok)
            })
        }
        up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata'
            })
        }
        yibu=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/effects名字"
                type:'homemodel/genup'
            })
        }
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}
                    <button onClick={this.up}>点我进行修改</button>
                     <button onClick={this.yibu}>点我调用异步</button>
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    
  • 解决跨域:在webpackrc文件中设置跨域并修改请求

     "proxy":{
            "/api":{
                 "target":"http://www.weather.com.cn",
                 "changeOrigin":true,
                 "pathRewrite":{
                   "^/api":"/"
                 }
            }
        }
    
    import request from '../utils/request'
    export function query(){
        return request('/api/data/cityinfo/101320101.html');
    }
    

dva-model异步请求

dva---model异步请求

  • 如果需要在model中进行异步请求的话需要在effects中的call进行异步操作的发送

    import * as homeapi from '../services/homeapi.js'
    export default {
        namespace:'homemodel',
        state:{
    		name:'xixi',
            age:18
        },
        //修改操作
        reducers:{
       		updata(state,payload){
                console.log('我是修改操作')
                return {...state,name:payload.data.text} //必须要有返回值
            }
    	},
        effects:{
            //使用Generator语法
    		*genup({payload},{put,call}){
                //在call中调用异步操作
               let api=yield call(homeapi.query)
               console.log(api)
            }
        }
    }
    
  • 页面内调用带有异步请求的effects

    import React,{Component} from 'react'
    import {connect} from 'dva'
    //* as 自己起的名字  相当于把当前模块下所有内容引用
    import * as homeapi from '../services/homeapi.js'
    class home extends Component{
        componentDidMount(){
            homeapi.query().then(ok=>{
    			console.log(ok)
            })
        }
        up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata'
            })
        }
        quest=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/effects名字"
                type:'homemodel/genup'
            })
        }
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}
                    <button onClick={this.up}>点我进行修改</button>
                    <button onClick={this.quest}>点我调用异步请求</button>
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    

dva-model异步请求修改state

dva---model异步请求

  • 可以通过put的方式修改state

     effects:{
            //使用Generator语法
    		*genup({payload},{put,call}){
                //在call中调用异步操作
               let api=yield call(homeapi.query)
               if(api.data){
                  consoel.log(api.data.weatherinfo.city)
                  yield put({
                      type:'updata',
                      data:{
                          obj:api.data.weatherinfo.city
                      }
                  })
               }
            }
        }
    
  • 创建修改reducers

    import * as homeapi from '../services/homeapi.js'
    export default {
        namespace:'homemodel',
        state:{
    		name:'xixi',
            age:18,
            obj:''//创建state
        },
        //修改操作
        reducers:{
       		updata(state,payload){
                console.log(payload.data.obj)
                return {...state,obj:payload.data.obj} //必须要有返回值
            }
    	},
        effects:{
            //使用Generator语法
    		*genup({payload},{put,call}){
                //在call中调用异步操作
               let api=yield call(homeapi.query)
               if(api.data){
                  consoel.log(api.data.weatherinfo.city)
                  yield put({
                      type:'updata',
                      data:{
                          obj:api.data.weatherinfo.city
                      }
                  })
               }
            }
        }
    }
    
  • 页面引用state数据给props并展示

    import React,{Component} from 'react'
    import {connect} from 'dva'
    //* as 自己起的名字  相当于把当前模块下所有内容引用
    import * as homeapi from '../services/homeapi.js'
    class home extends Component{
        componentDidMount(){
            homeapi.query().then(ok=>{
    			console.log(ok)
            })
        }
        up=()=>{
    		this.props.dispatch({
                //type:"命名空间名字/reducer名字"
                type:'homemodel/updata'
            })
        }
        quest=()=>{
    		this.props.dispatch({
                type:'homemodel/genup'
            })
        }
        render(){
            return (
                <div>
                    {/*3、使用*/}
                	home---{this.props.name}---{this.props.obj}
                    <button onClick={this.up}>点我进行修改</button>
                    <button onClick={this.quest}>点我调用异步请求</button>
                </div>
            )
        }
    }
    //1、设置数据
    let mapdata=(state)=>{
        return {
            //state.命名空间名字.xxx
            name:state.homemodel.name,
            //引用数据
            obj:state.homemodel.obj
        }
    }
    //2、传递HOC
    export default connect(mapdata)(home)
    

dva-subscription订阅

  • model中的subscription相当于一个监听器,可以监听路由变化,鼠标,键盘变化,服务器连接变化,状态变化等,这样在其中就可以根据不同的变化做出相应的处理,在这个subsription中的方法名是随意定的,每次变化都会一次去调用里面的所有方法,所以一边会加相应的判断

    subscriptions:{
        resize({dispatch,history}){
            //window.onresize是页面尺寸变化时触发的事件
            window.onresize=()=>{
                console.log('改变了')
            }
        }
    }
    
原文地址:https://www.cnblogs.com/cwl1025/p/13949127.html