React框架学习

180 阅读23分钟

06-第六阶段笔记

1、React入门

1.1、React简介

  1. 用于动态构建用户界面的 JavaScript 库(只关注于视图)
  2. 由Facebook开源

1.1.1、官网

  1. 英文官网: https://reactjs.org/
  2. 中文官网:react.docschina.org/

1.1.2、React的特点

  1. 声明式编码
  2. 组件化编码
  3. React Native 编写原生应用
  4. 高效(优秀的Diffing算法)

1.1.3、React高效的原因

  1. 使用虚拟(virtual)DOM, 不总是直接操作页面真实DOM。
  2. DOM Diffing算法, 最小化页面重绘。

1.2、React的基本使用

1.2.1、基本使用

<!-- 准备好一个容器,用于让react渲染用 -->
<div id="test"></div>
<!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script>
//下面一定要将javascript改为babel,含义是:让babel翻译script标签中的代码。 
<script type="text/babel">          
    //1.创建虚拟DOM
    //此处一定不要写引号,因为VDOM不是字符串!!!  
    const VDOM = <h1>Hello,React</h1>   
    //2.使用react语法将VDOM转为真实DOM,插入页面
    ReactDOM.render(VDOM,document.getElementById('test'))
</script>

1.2.2、相关js库

  1. react.js:React核心库。
  2. react-dom.js:提供操作DOM的react扩展库。
  3. babel.min.js:解析JSX语法代码转为JS代码的库。

1.2.3、创建虚拟DOM的两种方式

  1. 纯JS方式(一般不用)

    <script type="text/javascript">         
        //1.创建虚拟DOM
        const VDOM = (
            React.createElement('h1',{id:'title'},
            React.createElement('span',{},'Hello,React')))
    </script>
    
  2. JSX方式

    <script type="text/babel"> 
        //1.创建虚拟DOM
        const VDOM = (
            <h1 id="title">
                <span>Hello,React</span>
            </h1>
        )
    </script>
    

1.2.4、虚拟DOM与真实DOM

  1. React提供了一些API来创建一种 “特别” 的一般js对象

    //下面创建的就是一个简单的虚拟DOM对象
    const VDOM = React.createElement('xx',{id:'xx'},'xx')
    
  2. 虚拟DOM对象最终都会被React转换为真实的DOM

  3. 我们编码时基本只需要操作react的虚拟DOM相关数据, react会转换为真实DOM变化而更新界。

1.3、React JSX

1.3.1、JSX简介

  1. 全称: JavaScript XML

  2. react定义的一种类似于XML的JS扩展语法: JS + XML本质是JS方法的语法糖

    React.createElement(component,props, ...children)
    
  3. 作用: 用来简化创建虚拟DOM

    1. 写法:var ele =

      Hello JSX!

    2. 注意1:它不是字符串, 也不是HTML/XML标签
    3. 注意2:它最终产生的就是一个JS对象
  4. 标签名任意: HTML标签或其它标签

  5. 标签属性任意: HTML标签属性或其它

1.3.2、JSX基本语法规则

  1. 创建虚拟DOM时,不要用引号。

  2. 标签中想混入js表达式,需要用{}包裹。

  3. 根标签必须只有一个

  4. 标签必须闭合

  5. 样式的类名,不要用class,必须用className

  6. 内联的样式要用 style={{}}形式去写

  7. 标签可以随意的编写:

    1. 若标签首字母是【小写】的,则react会尝试将当前的jsx标签对应成一个html标签

      • 若对应成了,直接渲染,展示。
      • 若无法对应,直接报错!
    2. 若标签首字母是【大写】的,则react会查找Haha组件的定义的位置

      • 若找见了,直接渲染Haha组件
      • 若未找见,报错(Haha is not defined)

1.3.3、渲染虚拟DOM(元素)

  1. 语法:

    ReactDOM.render(virtualDOM, containerDOM)
    
  2. 作用: 将虚拟DOM元素渲染到页面中的真实容器DOM中显示

  3. 参数说明

    1. 参数一: 纯js或jsx创建的虚拟dom对象
    2. 参数二: 用来包含虚拟DOM元素的真实dom元素对象(一般是一个div)

1.4、模块与组件、模块化与组件化的理解

1.4.1、模块

  1. 理解:向外提供特定功能的js程序, 一般就是一个js文件
  2. 为什么要拆成模块:随着业务逻辑增加,代码越来越多且复杂。
  3. 作用:复用js, 简化js的编写, 提高js运行效率

1.4.2、组件

  1. 理解:用来实现局部功能效果的代码和资源的集合(html/css/js/image等等)
  2. 为什么要用组件: 一个界面的功能更复杂
  3. 作用:复用编码, 简化项目编码, 提高运行效率

1.4.3、模块化

  • 当应用的js都以模块来编写的, 这个应用就是一个模块化的应用

1.4.4、组件化

  • 当应用是以多组件的方式实现, 这个应用就是一个组件化的应用

2、React面向组件编程

2.1、基本理解和使用

2.1.1、使用React开发者工具调试

  • React Developer Tools (Mate提供(原facebook))

2.1.2、函数式组件

  • 函数式组件实例:

  • <script type="text/babel"> 
        //1.定义组件(函数式组件)
        function Demo(){
            console.log(this); //此处的this是undefined,因为经过babel的编译后,开启了严格模式。
            return <h2>函数定义的组件(适用于【简单组件】)</h2>
        }
        //2.渲染组件到页面
        ReactDOM.render(
            <Demo/>,document.getElementById('test')
         )
    </script>
    
  • 函数式组件标签的渲染基本流程

    1. React发现了标签,去寻找Demo组件定义的位置,发现Demo是用函数定义的。
    2. React调用Demo并获取Demo返回的虚拟DOM,随后转为真实DOM,随后渲染到页面。

2.1.3、类式组件

  • 类式组件实例

  • <script type="text/babel"> 
    	//定义组件,extends 继承
    	class Demo extends React.Component{
    		//render是放在Demo的原型对象上,是给Demo的实例对象用的。
    		render(){
    			console.log(this);
                 //this是Demo的实例对象 <==> Demo组件实例对象
    			return <h2>类定义的组件(适用于【复杂组件】)</h2>
    			}
    		}
    	//渲染组件到页面			
    	ReactDOM.render(
        	<Demo/>,document.getElementById('test')
    	)
    </script>
    
  • 类组件标签的渲染基本流程

    1. React发现了标签,去寻找Demo组件定义的位置,发现Demo是用类定义的。
    2. React new了一个Demo实例对象--d
    3. 通过d调用到了Demo原型上的render方法,并获取到了返回的虚拟DOM,随后转为真实DOM,放在页面。

2.1.3、注意内容

  1. 组件名必须首字母大写
  2. 虚拟DOM元素只能有一个根元素
  3. 虚拟DOM元素必须有结束标签

2.2、组件的三大核心属性一:state

2.2.1、state简介

  1. state是组件对象最重要的属性, 值是对象(可以包含多个key-value的组合)
  2. 组件被称为"状态机", 通过更新组件的state来更新对应的页面显示(重新渲染组件)

2.2.1、state实例应用一(原始版)

<script type="text/babel"> 
	//1.定义组件----类式组件
	class Weather extends React.Component{
		//构造器调用几次?-------- 看你组件用几次
         constructor(props){
            console.log('constructor');
            super(props)
            //初始化状态,isHot用于标识天气热不热
            this.state = {isHot:true,wind:'微风'} 
            //解决this指向问题
            this.changeWeather = this.changeWeather.bind(this) 
        }
        //changeWeather调用几次?-------- 看你点几次
        changeWeather(){
            console.log('changeWeather');
        //若构造器中不做处理,那么下面的this是undefined,因为changeWeather
        //不是通过实例调用的,而是作为点击的回调去使用,
        //且类中的方法自动开启了严格模式。
            //console.log('changeWeather的this是',this); 
            //严重注意:状态(state)中的值是不能直接修改的!!!!
             //下面这一行就是直接修改
            //this.state.isHot = true
            //获取原来的state中的isHot值
            const {isHot} = this.state
            //更新状态
             //此处更新状态是一个合并的动作,不是替换
            this.setState({isHot:!isHot}) 	
        }
        //render调几次?--------- 1+n次(n是更新状态的次数)
        render(){
            console.log('render');
            return (
                <h1 onClick={this.changeWeather}>
                    今天天气很{this.state.isHot ? '炎热' : '凉爽'}
                </h1>)
        }
    }	
//2.渲染组件			
    ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>

2.2.2、state实例应用二(简化版)

<script type="text/babel"> 
	//1.定义组件----类式组件
	class Weather extends React.Component{
		state = {isHot:true,wind:'微风23'} //初始化状态
		//事件的回调都需要写成赋值语句+箭头函数的形式
		changeWeather = ()=>{
			console.log('changeWeather');
			const {isHot} = this.state
			this.setState({isHot:!isHot})
		}
	render (){
		return (
			<h1 onClick={this.changeWeather}>
				今天天气很{this.state.isHot ? '炎热' : '凉爽'}
			</h1>
		)
	}
}
//2.渲染组件			
	ReactDOM.render(<Weather/>,document.getElementById('test'))
</script>

2.2.3、state总结

  1. 组件中render方法中的this为组件实例对象

  2. 组件自定义的方法中this为undefined,如何解决?

    1. 强制绑定this: 通过函数对象的bind()
    2. 箭头函数
  3. 状态数据,不能直接修改或更新

2.3、组件三大核心属性二: props

2.3.1、props简介

  • 每个组件对象都会有props(properties的简写)属性
  • 组件标签的所有属性都保存在props中

2.3.2、props的基本使用

  • <script type="text/babel"> 
    	//定义组件(类)
    	class Person extends React.Component{
    		render(){
    			console.log(this);
    			const {name,age,sex} = this.props
    			return (
    				<ul>
    					<li>姓名:{name}</li>
    					<li>性别:{sex}</li>
    					<li>年龄:{age}</li>
    				</ul>
    			)
    		}
    	}
    	//渲染组件
    	ReactDOM.render(
            <Person name="tom" sex="女" age="18"/>,
            document.getElementById('test')
    	)
    	const p1 = {
    		name:'程老师',
    		sex:'男',
    		age:18
    	}
    	//下面的...p1,并不是原生js里的{...p1},
        //babel+react环境就可以让展开运算符展开一个对象,但是仅仅适用于传递标签属性!!
    	ReactDOM.render(
            <Person {...p1}/>,document.getElementById('test2')
    	)
    </script>
    

2.3.3、类组件对props进行限制

  • //类组件有this,可以定义在组件内部
    static propTypes = {
    	name:PropTypes.string, //限制name必须为字符串类型
    	sex:PropTypes.string.isRequired,//限制必要属性,且必须为字符串类型
    	age:PropTypes.number,//限制age必须为数值类型
    	address:PropTypes.string, //限制address必须为字符串类型
    }
    //对传给Person组件的props进行默认值的设置
    static defaultProps = {
    	address:'中国'
    }
    

2.3.4、函数式组件使用props

  • //函数内没有this,所有只能定义在函数组件外
    Person.propTypes = {
    	name:PropTypes.string, //限制name必须为字符串类型
    	sex:PropTypes.string.isRequired,//限制必要属性,且必须为字符串类型
    	age:PropTypes.number,//限制age必须为数值类型
    	address:PropTypes.string, //限制address必须为字符串类型
    }
    //对传给Person组件的props进行默认值的设置
    Person.defaultProps = {
    	address:'中国'
    }
    

2.4、组件三大核心属性三: ref

组件内的标签可以定义ref属性来标识自己

2.4.1、字符串形式的ref

  • //定义组件
    class Demo extends React.Component{
    	show = ()=>{
    		//获取用户的输入,input1是真实DOM节点!!
    		const {input1} = this.refs
    		//提示数据
    		alert(input1.value)
    	}
    	render(){
    		return (
    			<div>
    				<input ref="input1"/>
    				<button onClick={this.show}>点我提示数据</button>
    		</div>
    		)
    	}
    }
    

2.4.2、回调形式的ref(常用)

  • //定义组件
    class Demo extends React.Component{
    	showData = ()=>{
    		//获取用户的输入,input1是真实DOM节点!!
    		const {input1} = this
    		//提示数据
    		alert(input1.value)
    	}
    	render(){
    		return (
    			<div>
    				<input ref={c => this.input1 = c} type="text" /
    				<button onClick={this.showData}>点我提示数据</button>
    			</div>
    		)
    	}
    }
    

2.4.3、createRef创建ref容器

  • //定义组件
    class Demo extends React.Component{
    	//使用createRef,可以创建一个存储节点的容器,此容器是专人专用的。
    	//container是放在组件实例身上的
    	container1 = React.createRef()
    	container2 = React.createRef()
    	showData = ()=>{
    		const {current} = this.container1
    		alert(current.value)
    	}
    	render(){
    		return (
    			<div>
    				<input ref={this.container1}  type="text" />
    				<button onClick={this.showData}>点我提示数据</button>
    				<input ref={this.container2} onBlur={this.showData2} type="text"/>
    
    			</div>
    		)
    	}
    }
    

2.5、React事件处理

  1. 通过onXxx属性指定事件处理函数(注意大小写)

    1. React使用的是自定义(合成)事件, 而不是使用的原生DOM事件
    2. React中的事件是通过事件委托方式处理的(委托给组件最外层的元素)
  2. 通过event.target得到发生事件的DOM元素对象

  3. //定义组件
    class Demo extends React.Component{
    	showData2 = (event)=>{
    		const {value} = event.target
    		alert(value)
    	}
    	render(){
    		return (
    			<div>
    				<input onBlur={this.showData2} type="text" placeholder="失去焦点提示输入"/>
    			</div>
    		)
    	}
    }
    

2.6、收集表单数据

  1. 非受控组件

    • //定义组件
      //非受控的概念:现用现取
      class Login extends React.Component{
      	handleSubmit = (event)=>{
      		event.preventDefault()
      		console.log(this.user.value);
      		console.log(this.pwd.value);
      	}
      	render(){
      		return (
      			<form onSubmit={this.handleSubmit}>
      				用户名:<input ref={c => this.user = c} type="text" placeholder="用户名"/>
      				密码:<input ref={c => this.pwd = c}  type="password" placeholder="密码"/>
      				<button>登录</button>
      			</form>
      		)
      	}
      }
      
  2. 受控组件

    • //定义组件
      //受控的概念:组件中输入类的DOM,随着用户的输入,将输入的值维护到state中
      class Login extends React.Component{
      	state = {
      		username:'',
      		password:''
      	}
      	//点击登录的回调
      	handleSubmit = (event)=>{
      		event.preventDefault()
      		const {username,password} = this.state	
      		console.log(username,password);
      	}
      	//存储用户名到状态
      	saveUsername = (event)=>{
      		this.setState({username:event.target.value})
      	}
      	//存储密码到状态
      	savePwd = (event)=>{
      		this.setState({password:event.target.value})
      	}				
      	render(){
      		return (
      			<form onSubmit={this.handleSubmit}>
      				用户名:<input onChange={this.saveUsername} type="text" placeholder="用户名"/>
      				密码:<input onChange={this.savePwd} type="password" placeholder="密码"/>
      				<button>登录</button>
      			</form>
      		)
      	}
      }
      

2.7、高阶函数和函数的柯里化

2.7.1、高阶函数

如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数

  1. 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
  2. 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
  3. 常见的有:Promise、setTimeout、arr.forEach().....

2.7.2、函数的柯里化

通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式

  • function add(a){
    	return (b)=>{
    		return (c)=>{
    			return a+b+c
    		}
    	}
    }
    

2.8、类式组件中的构造器

  1. 类式组件中的构造器,完全可以省略掉
  2. 若在类式组件中写了构造器,那就必须调用super
  3. 调用super时,如果不传props,那么在构造器中,通过this.props是不可以访问props的
  4. class Demo extends React.Component{
    	constructor(props) {
    		super(props)
    		this.state = {isHot:false}
    		console.log('@@@',this.props);
    	}
    	render(){
    		console.log('#####',this.props);
    		return <h2>你好,我是Demo组件,今天天很{this.state.isHot ? '炎热' : '凉爽'}</h2>
    	}
    }
    

2.9、组件的生命周期

  1. 组件从创建到死亡它会经历一些特定的阶段。
  2. React组件中包含一系列勾子函数(生命周期回调函数), 会在特定的时刻调用。
  3. 我们在定义组件时,会在特定的生命周期回调函数中,做特定的工作。

2.9.1、生命周期的三个阶段(旧)

1654914443858

  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

    1. constructor() ---- 构造器

    2. componentWillMount() ---- 组件将要挂载

    3. render() ---- 组件初次渲染+更新

    4. componentDidMount() ---- 组件挂载完毕

      • 一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
  2. 更新阶段: 由组件内部this.setSate() 或父组件render触发this.forceUpdate()(备注:强制更新)

    1. shouldComponentUpdate() ---- 组件更新的“阀门” 注意:强制更新不走“阀门”
    2. componentWillUpdate() ---- 组件将要更新
    3. render() ---- 组件初次渲染+更新
    4. componentDidUpdate() ---- 组件更新完毕
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount() ---- 组件将要卸载(常用)

      • 一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
  4. //定义组件
    class Sum extends React.Component{
    	//构造器----调1次
    	constructor(){
    		console.log('---constructor---');
    		super()
    		this.state = {count:0}
    	}
    	//加1的回调
    	add = ()=>{
    		const {count} = this.state
    		this.setState({count:count+1})
    	}
    	//卸载的回调
    	death = ()=>{
            ReactDOM.unmountComponentAtNode(
            	document.getElementById('test')
            )
    	}
    	//强制更新的回调
    	force = ()=>{
    		this.forceUpdate()
    	}
    	//组件将要挂载---调1次
    	componentWillMount(){
    		console.log('---componentWillMount---');
    	}
    	//组件挂载完毕---调1次
    	componentDidMount(){
    		console.log('---componentDidMount---');
    	}
    	//组件将要卸载---调1次
    	componentWillUnmount(){
    		console.log('---componentWillUnmount---');
    	}
    	//组件更新的“阀门”
    	shouldComponentUpdate(){
    		console.log('---shouldComponentUpdate---');
    		return true
    	}
    	//组件将要更新----调用n次,n是更新的次数
    	componentWillUpdate(){
    		console.log('---componentWillUpdate---');
    	}
    	//组件更新完毕----调用n次,n是更新的次数
    	componentDidUpdate(){
    		console.log('---componentDidUpdate----');
    	}
    	
    	componentWillReceiveProps(){
    		console.log('---componentWillReceiveProps---');
    	}
    	//组件初次渲染+更新---调1+n次
    	render(){
    		console.log('---render---');
    		return(
    			<div>
    				<h2>当前求和为:{this.state.count},值是{this.props.a}</h2>
    				<button onClick={this.add}>点我+1</button>
    				<button onClick={this.death}>卸载组件</button>
    				<button onClick={this.force}>强制更新一下</button>
    			</div>
    		)
    	}
    }
    //渲染组件
    ReactDOM.render(<Car/>,document.getElementById('test'))
    

2.9.2、生命周期流程图(新)

  1. 初始化阶段: 由ReactDOM.render()触发---初次渲染

    1. constructor()
    2. getDerivedStateFromProps
    3. render()
    4. componentDidMount()
  2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发

    1. getDerivedStateFromProps
    2. shouldComponentUpdate()
    3. render()
    4. getSnapshotBeforeUpdate
    5. componentDidUpdate()
  3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发

    1. componentWillUnmount()

2.9.3、重要的勾子

  1. render:初始化渲染或更新渲染调用
  2. componentDidMount:开启监听, 发送ajax请求
  3. componentWillUnmount:做一些收尾工作, 如: 清理定时器

2.9.4、即将废弃的勾子

  1. componentWillMount
  2. componentWillReceiveProps
  3. componentWillUpdate

即将废弃的钩子现在使用会出现警告,下一个大版本需要加上UNSAFE_前缀才能使用,以后可能会被彻底废弃,不建议使用。

2.10、虚拟DOM与DOM Diffing算法

经典面试题:

  1. React/Vue中的key有什么作用?(key的内部原理是什么?)

    虚拟DOM中key的作用:

    1. 简单的说:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用

    2. 详细中说:当状态中的数据发生变化时,React会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:

      • 旧虚拟DOM中找到与新虚拟DOM相同的key

        1. 若虚拟DOM中内容没变,直接使用之前的真实DOM
        2. 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
      • 旧虚拟DOM中未找到与新虚拟DOM相同的key

        1. 根据数据创建新的真实DOM,随后渲染到页面
  2. 为什么遍历列表时,key最好不要用index?

    用index作为key可能引发的问题:

    1. 若对数据进行:逆序添加,逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新 ===> 界面效果没问题,但效率低
    2. 如果结构中还包含输入类的DOM,会产生错误的DOM更新 ===> 界面有问题
    3. 注意!如果不存在对数据的逆序添加,逆序删除等破坏顺序操作,仅用于渲染列表用于展示,使用index做为key是没有问题的。
  3. 开发中如何选择key?

    1. 最好使用每条数据的唯一标识作为key,比如id、手机号、身份证号、学号等唯一值。
    2. 如果确定只是简单的展示数据,用index也是可以的。

3、React应用(基于React脚手架)

3.1、使用create-react-app创建react应用

3.1.1、react脚手架

  1. xxx脚手架: 用来帮助程序员快速创建一个基于xxx库的模板项目

    1. 包含了所有需要的配置(语法检查、jsx编译、devServer…)
    2. 下载好了所有相关的依赖
    3. 可以直接运行一个简单效果
  2. react提供了一个用于创建react项目的脚手架库: create-react-app

  3. 项目的整体技术架构为: react + webpack + es6 + eslint

  4. 使用脚手架开发的项目的特点: 模块化, 组件化, 工程化

3.1.2、创建项目并启动

  • 第一步,全局安装:npm i -g create-react-app
  • 第二步,切换到想创项目的目录,使用命令:create-react-app hello-react
  • 第三步,进入项目文件夹:cd hello-react
  • 第四步,启动项目:npm start

3.1.3、react脚手架项目结构

  • public文件夹静态资源文件夹
    favicon.icon网站页签图标
    index.html主页面
    logo192.pnglogo图
    logo512.pnglogo图
    manifest.json应用加壳的配置文件
    robots.txt爬虫协议文件
  • src文件夹源码文件夹
    App.cssApp组件的样式
    App.jsApp组件(App是所有组件的外壳组件,它包着所有的组件)
    App.test.js用于给App做测试
    index.css样式
    index.js入口文件
    logo.svglogo图
    reportWebVitals.js页面性能分析文件(需要web-vitals库的支持)
    setupTests.js组件单元测试的文件(需要jest-dom库的支持)

3.1.4、功能界面的组件化编码流程(通用)

  1. 拆分组件: 拆分界面,抽取组件

  2. 实现静态组件: 使用组件实现静态页面效果

  3. 实现动态组件

    1. 动态显示初始化数据

      1. 数据类型
      2. 数据名称
      3. 保存在哪个组件?
    2. 交互(从绑定事件监听开始)

4、React ajax

  1. React本身只关注于界面, 并不包含发送ajax请求的代码
  2. 前端应用需要通过ajax请求与后台进行交互(json数据)
  3. react应用中需要集成第三方ajax库(或自己封装)

4.1、常用的ajax请求库

  1. jQuery: 比较重, 如果需要另外引入不建议使用

  2. axios: 轻量级, 建议使用

    1. 封装XmlHttpRequest对象的ajax
    2. promise风格
    3. 可以用在浏览器端和node服务器端
  3. Fetch:原生函数,没人用

    1. 不再使用XmlHttpRequest对象提交ajax请求
    2. 采用关注分离,老版本浏览器可能不支持

4.2、react中使用axios

4.2.1、文档

4.2.2、相关API(实战案例)

  • github搜索案例
  • //定义组件
    export default class Search extends Component {
    	search = ()=>{
    		const {keyWord} = this
          	//使用axios发送请求  
    		axios.get(`http://localhost:3000/search/users?q=${keyWord.value}`).then(
    			response => {
    				console.log('成功了',response.data);
    			},
    			error => {
    				console.log('失败了',error);
    			}
    		)
    	}
    	render() {
    		return (
    			<section className="jumbotron">
    				<h3 className="jumbotron-heading">Github用户搜索</h3>
    				<div>
    					<input ref={c => this.keyWord = c} type="text" placeholder="请输入用户名"/>&nbsp;
    					<button onClick={this.search}>搜索</button>
    				</div>
    			</section>
    		)
    	}
    }
    

4.3、react脚手架配置代理总结

4.3.1、方法一

在package.json中追加如下配置

"proxy":"http://localhost:5000"

说明:

  1. 优点:配置简单,前端请求资源时可以不加任何前缀。
  2. 缺点:不能配置多个代理。
  3. 工作方式:上述方式配置代理,当请求了3000不存在的资源时,那么该请求会转发给5000 (优先匹配前端资源)

4.3.2、方法二

  1. 第一步:创建代理配置文件

    src下创建配置文件:src/setupProxy.js
    
  2. 编写setupProxy.js配置具体代理规则:

    //新版配置代码
    const {createProxyMiddleware} = require('http-proxy-middleware')
    
    module.exports = function(app){
      app.use(
        //api1是需要转发的请求(所有带有/api1前缀的请求都会转发给5000)
        createProxyMiddleware('/api1',{
          target:'http://localhost:5000',
          //控制服务器接收到的请求头中host字段的值
          //设置为true时,服务器收到的请求头中的host为:localhost:5000
          //设置为false时,服务器收到的请求头中的host为:localhost:3000
          //默认值为false,但我们一般将changeOrigin值设为true
          changeOrigin:true,
          //去除请求前缀,保证交给后台服务器的是正常请求地址(必须配置)
          pathRewrite:{'^/api1':''}
        }),
        createProxyMiddleware('/api2',{
          target:'http://localhost:5001',
          changeOrigin:true,
          pathRewrite:{'^/api2':''}
        })
      )
    }
    

说明:

  1. 优点:可以配置多个代理,可以灵活的控制请求是否走代理。
  2. 缺点:配置繁琐,前端请求资源时必须加前缀。

4.4、消息订阅-发布机制

  1. 工具库: PubSubJS

  2. 下载: npm install pubsub-js --save

  3. 使用:

    • import PubSub from 'pubsub-js' //引入
      PubSub.subscribe('delete', function(data){ }); //订阅
      PubSub.publish('delete', data) //发布消息
      

5、React路由

5.1. 相关理解

5.1.1. SPA的理解

  1. 单页Web应用(single page web application,SPA)。
  2. 整个应用只有一个完整的页面
  3. 点击页面中的链接不会刷新页面,只会做页面的局部更新。
  4. 数据都需要通过ajax请求获取, 并在前端异步展现。

5.1.2、路由的理解

1、什么是路由?
  1. 一个路由就是一个映射关系(key:value)
  2. key为路径, value可能是function或component

小诀窍:路由路由,根据路径,由我给你展示某个组件

2、路由的分类

后端路由:

  1. 理解: value是function, 用来处理客户端提交的请求。
  2. 注册路由: router.get(path, function(req, res))
  3. 工作过程:当node接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据

前端路由:

  1. 浏览器端路由,value是component,用于展示页面内容。
  2. 注册路由:
  3. 工作过程:当浏览器的path变为/test时, 当前路由组件就会变为Test组件

5.2、react-router-dom路由组件

5.2.1、简介

  1. react的一个插件库。
  2. 专门用来实现一个SPA应用。
  3. 基于react的项目基本都会用到此库。

5.2.2、react-router-dom相关API

  1. 引入方式,分别引入

    import {NavLink,Route,Switch,Redirect} from 'react-router-dom'
    
  2. Link ---- 路径的切换

    <Link className="list-group-item" to="/about">About</Link>
    
  3. Route ---- 注册路由

    <Route path="/about" component={About}/>
    
  4. NavLink ---- 路径切换,带有高亮效果(active)

    //activeClassName ----  修改选中样式类名
    <NavLink activeClassName="demo" className="list-group-item" to="/about">About</NavLink>
    
  5. Switch ---- 开启单一匹配,提高效率

    <Switch>
    	<Route path="/about" component={About}/>
    </Switch>
    
  6. Redirect ---- 默认路由,一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由

    <Switch>
    	<Route path="/about" component={About}/>
    	<Route path="/home" component={Home}/>
    	<Redirect to="/about"/>
    </Switch>
    
  7. BrowserRouter ---- 包裹在组件最外面

    ReactDOM.render(
    	<BrowserRouter>
    		<App/>
    	</BrowserRouter>,
    document.getElementById('root'))
    
  8. HashRouter ---- 包裹在组件的最外面(路径中加一个#,#后面路径不发送服务器)

5.2.3、路由的基本使用

  1. 安装react-router-dom库

  2. 明确好界面中的导航区、展示区,准备好Home组件、About组件

  3. 导航区的a标签改为Link标签

    <Link to="/xxxxx">Demo</Link>
    
  4. 示区写Route标签进行路径的匹配

    <Route path='/xxxx' component={Demo}/>
    
  5. 的最外侧包裹了一个或

5.3、路由其他相关知识

5.3.1、路由组件与一般组件

  1. 写法不同

    • 一般组件:
    • 路由组件:
  2. 存放位置不同:

    • 一般组件:components
    • 路由组件:pages
  3. 接收到的props不同:

    • 一般组件:写组件标签时传递了什么,就能收到什么

    • 路由组件:接收到三个固定的属性

      1. history:

        • go: ƒ go(n) ---- 前进或后退指定步数
        • goBack: ƒ goBack() ---- 后退
        • goForward: ƒ goForward() ---- 前进
        • push: ƒ push(path, state) ---- 有痕迹跳转
        • replace: ƒ replace(path, state) ---- 无痕跳转
      2. location:

        • pathname: "/about" ---- 当前路径
        • search: ""
        • state: null
      3. match:

        1. isExact: true
        2. params: {}
        3. path: "/about" ---- 当前路径
        4. url: "/about" ---- 当前路径

5.3.2、解决多级路径刷新页面样式丢失的问题

  1. public/index.html 中 引入样式时不写 ./ 写 / (常用)
  2. public/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)
  3. 使用HashRouter

5.3.3、路由的严格匹配与模糊匹配

  1. 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】,且顺序要一致)

  2. 开启严格匹配:

    <Route exact={true} path="/about" component={About}/>
    
  3. 严格匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由

5.3.4、嵌套路由

  1. 注册子路由时要写上父路由的path值
  2. 路由的匹配是按照注册路由的顺序进行的

5.3.4、withRouter非路由组件挂载路由属性

withRouter可以在一个非路由组件上添加上路由组件独有的三大属性、history、location、match

  1. 引入

    import {withRouter} from 'react-router-dom'
    
  2. 正常编写组件

    class Header extends Component{
    	....
    }
    
  3. 组件暴露时,调用withRouter函数加工组件

    export default withRouter(Header)
    

5.4、向路由组件传递参数

5.4.1、params参数

  1. 路由链接(携带参数):

    <Link to='/demo/test/tom/18'}>详情</Link>
    
  2. 注册路由(声明接收):

    <Route path="/demo/test/:name/:age" component={Test}/>
    
  3. 接收参数:

    this.props.match.params
    

5.4.2、search参数

  1. 路由链接(携带参数):

    <Link to='/demo/test?name=tom&age=18'}>详情</Link>
    
  2. 注册路由(无需声明,正常注册即可):

    <Route path="/demo/test" component={Test}/>
    
  3. 接收参数:

    this.props.location.search
    
  4. 备注:获取到的search是urlencoded编码字符串,需要借助querystring解析

5.4.3、state参数

  1. 路由链接(携带参数):

    <Link to={{pathname:'/demo/test',state:{name:'tom',age:18}}}>详情</Link>
    
  2. 注册路由(无需声明,正常注册即可):

  3. <Route path="/demo/test" component={Test}/>
    
  4. 接收参数:

    this.props.location.state
    
  5. 备注:刷新也可以保留住参数

6、redux

6.1、 redux理解

6.1.1、学习文档

  1. 英文文档: redux.js.org/
  2. 中文文档: www.redux.org.cn/
  3. Github: github.com/reactjs/red…

6.1.2、redux是什么

  1. redux是一个专门用于做状态管理的JS库(不是react插件库)。
  2. 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
  3. 作用: 集中式管理react应用中多个组件共享的状态。

6.1.3、什么情况下需要使用redux

  1. 某个组件的状态,需要让其他组件可以随时拿到(共享)。
  2. 一个组件需要改变另一个组件的状态(通信)。
  3. 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。

6.1.4、redux工作流程

6.2、redux的三个核心概念

6.2.1、action

  1. 动作的对象

  2. 包含2个属性

    • type:标识属性, 值为字符串, 唯一, 必要属性
    • data:数据属性, 值类型任意, 可选属性
  3. 例子:

    { type: 'ADD_STUDENT',data:{name: 'tom',age:18} }
    

6.2.2、reducer

  1. 用于初始化状态、加工状态。
  2. 加工时,根据旧的state和action, 产生新的state的纯函数* *

6.2.3、store

  1. 将state、action、reducer联系在一起的对象

  2. 如何得到此对象?

    import {createStore} from 'redux'
    import reducer from './reducers'
    const store = createStore(reducer)
    
  3. 此对象的功能?

    getState():   //得到state
    dispatch(action):   //分发action, 触发reducer调用, 产生新的state
    subscribe(listener):   //注册监听, 当产生了新的state时, 自动调用  
    

6.3、redux的核心API

6.3.1、createstore()

作用:创建包含指定reducer的store对象

6.3.2、store对象

  1. 作用: redux库最核心的管理对象

  2. 它内部维护着:

    1. state
    2. reducer
  3. 核心方法

    getState()
    dispatch(action)
    subscribe(listener)
    
  4. 具体编码:

    store.getState()
    store.dispatch({type:'INCREMENT', number})
    store.subscribe(render)
    

6.3.3、applyMiddleware()

作用:应用上基于redux的中间件(插件库)

6.3.4、combineReducers()

作用:合并多个reducer函数

6.4、实战案例:求和

6.4.1、案例步骤:

  1. 去除Count组件自身的状态

  2. src下建立redux文件夹,文件夹包含一下文件

    1. store.js ----> redux库最核心的管理对象
    2. reducer.js ----> 用于初始化状态、加工状态。
    3. action.js ----> 专门用于创建action对象
    4. constant.js ----> 放置编码容易疏忽写错action中的type
  3. action.js:

    1. 创建工动作对象,包含两个属性 type 和 data

      export function createIncrementAction(number){
      	return {type:'add',data:number} 
      }
      
  4. store.js:

    1. 引入redux中的createStore函数,创建一个store

      //引入createStore,用于创建store对象
      import {createStore} from 'redux'
      //引入为Count组件服务的reducer,用于:初始化状态、加工状态
      import countReducer from './count_reducer'
      
    2. createStore调用时要传入一个为其服务的reducer

      export default createStore(countReducer)
      
    3. 记得暴露store对象

  5. reducer.js:

    1. reducer的本质是一个函数,接收:preState,action,返回加工后的状态

    2. reducer有两个作用:初始化状态,加工状态

    3. reducer被第一次调用时,是store自动触发的,传递的preState是undefined

      export default function Reducer(preState=0,action){
      	//从action对象中获取type和data
      	const {type,data} = action
      	switch (type) {
      		case 'add': //如果是加
      			return preState + data
      		case 'sub': //如果是减
      			return preState - data
      		default: //如果是初始化
      			return preState
      	}
      }
      
  6. 在项目组件中引入store和action

    import store from '../../redux/store'
    import {addAction,subAction} from '../../redux/count_action'
    
  7. 在index.js中检测store中状态的改变,一旦发生改变重新渲染

    import store from './redux/store'
    //检测redux中状态的改变
    store.subscribe(()=>{
    	ReactDOM.render(<App/>,document.getElementById('root'))
    })
    

6.5、react-redux

  • 一个react插件库
  • 专门用来简化react应用中使用redux(反而复杂)

6.5.1、react-Redux将所有组件分成两大类

  • UI组件

    1. 只负责 UI 的呈现,不带有任何业务逻辑
    2. 通过props接收数据(一般数据和函数)
    3. 不使用任何 Redux 的 API
    4. 一般保存在components文件夹下
  • 容器组件

    1. 负责管理数据和业务逻辑,不负责UI的呈现
    2. 使用 Redux 的 API
    3. 一般保存在containers文件夹下

6.5.2、相关API

  1. Provider:让所有组件都可以得到state数据

    <Provider store={store}>
      <App />
    </Provider>
    
  2. connect:用于包装 UI 组件生成容器组件

    import { connect } from 'react-redux'
      connect(
        mapStateToprops,
        mapDispatchToProps
      )(Counter)
    
  3. mapStateToprops:将外部的数据(即state对象)转换为UI组件的标签属性

    const mapStateToprops = function (state) {
      return {
        value: state
      }
    }
    
  4. mapDispatchToProps:将分发action的函数转换为UI组件的标签属性

6.6、使用上redux调试工具

6.6.1、 安装chrome浏览器插件

  • Reduo Dev Tools

6.6.2、下载工具依赖包

  • npm install --save-dev redux-devtools-extension
    

6.7、纯函数和高阶函数

6.7.1. 纯函数

  1. 一类特别的函数: 只要是同样的输入(实参),必定得到同样的输出(返回)

  2. 必须遵守以下一些约束

    • 不得改写参数数据
    • 不会产生任何副作用,例如网络请求,输入和输出设备
    • 不能调用Date.now()或者Math.random()等不纯的方法
  3. redux的reducer函数必须是一个纯函数

7.8.2. 高阶函数

  1. 理解: 一类特别的函数

    1. 情况1: 参数是函数
    2. 情况2: 返回是函数
  2. 常见的高阶函数:

    1. 定时器设置函数
    2. 数组的forEach()/map()/filter()/reduce()/find()/bind()
    3. promise
    4. react-redux中的connect函数
  3. 作用: 能实现更加动态, 更加可扩展的功能