react
- React简介
- React开发背景
- 虚拟DOM----减少更新次数,减少更新区域
- 为什么虚拟dom会提高性能
- diff算法的作用
- React的diff算法
- React特点
- React组件特性
- React依赖包下载
- React开发环境搭建
- React--根DOM节点
- React--JSX
- JSX优点
- ReactDOM.render()
- JSX--注释
- jsx独立文件使用
- JSX绑定属性
- 定义样式
- 引用外部样式
- jsx规则扩展
- React列表渲染
- React列表渲染--keys
- 如何让render重新渲染
- 对象取值
- React遍历对象
- 模块与模块化
- 组件与组件化
- 组件的概念
- 定义自定义组件--函数组件/无状态组件
- 创建类组件
- 定义父子组件
- props
- 逆向传值
- 同级传值---pubsub-js
- this.props.children
- context上下文对象--跨组件传值
- 状态机
- constructor--super
- 为什么不用工厂方式创建组件
- 字符串类型标签解析
- Refs转发
- ref三种用法
- React受控组件
- React组件生命周期
- React事件处理
- 事件处理--传递参数
- 事件处理--修改this指向
- React条件渲染
- React脚手架使用
- 切换npm 镜像路径
- React脚手架使用目录结构
- React脚手架使用目录结构:
- 创建组件
- 多行标签
- 使用图片
- react脚手架props验证
- styled-components
- 强制刷新forceUpdate()
- react前端配置正向代理跨域
- 路由
- 路由模式-HashRouter和BrowserRouter
- 路由-Link与Switch
- 路由的基本使用
- exact精准匹配
- 路由--重定向
- 二级路由
- 路由--js跳转
- 路由--withRouter
- 路由--params传参
- 路由--state传参
- 路由render渲染写法
- 路由render渲染写法传递数据
- 路由验证
- 高阶组件HOC
- 高阶组件HOC接受props
- 高阶组件HOC--反向继承
- REDUX
- redux使用场景
- redux三大原则
- redux常用方法和概念
- redux使用
- redux修改数据
- redux修改数据传递参数
- 使用actionCreator统一创建action
- redux封装派发动作名(action任务名
- redux封装reducer模块
- redux合并reducer
- react-redux
- react-redux使用
- 性能优化
- 修改react脚手架端口
- 启动react项目
- react脚手架使用confirm报错问题
- react脚手架使用--配置路径别名
- fetch ---es6
- fetch VS ajax VS axios
- qs
- react与typescript
- umiJS
- 约定式路由
- 约定式路由---动态路由
- 约定式路由---二级路由
- 模拟数据json-server
- 不可变对象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---Map
- immutable---List
- immutable在react中使用
- immutable在redux中使用
- redux-immutable
- redux-immutable修改
- dva
- 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
- 定义路由导航
- 在dva中使用路由导航要从dva/router中进行引用
- 声明式:
- 编程式:
- 定义路由模式
- 切换HashHistory为BrowserHistory
- 需要下载history依赖:npm install --save history
- 然后修改入口文件(即src下的index.js文件):
- 定义组件
- 组件在components下进行定义
- 在需要的地方引用使用
- 定义组件的方式可以定义无状态组件,也可以是class类组件
- 无状态组件的创建形式使代码的可读性更好,并且减少了大量冗余的代码,精简至只有render方法,大大的增强了编写一个组件的便利
- 无状态组件的特点
- 组件不会被实例化,整体渲染性能得到提升
- 因为组件被精简成一个render方法的函数实现的,无状态就不会再有组件实例化的过程,无实例化过程也就不需要分配多余的内存,从而性能得到一定的提升
- 组件不能访问this对象
- 无状态组价由于没有实例化过程,所以无法访问组件this中的对象,若想访问this就不能使用这种形式创建组件
- 组件无法访问生命周期的方法
- 定义Model
- model用来处理数据和逻辑,可以把数据和交互都放到model中方便使用
- 在model文件夹中创建model文件
- 引用Model
- 在全局文件index.js中进行引用
- 使用Model
- 在需要使用的组件中使用connect进行连接
- 读取Model数据
- 定义方法读取数据
- dva-reducers
- 修改---Model数据
- 在model文件中定义reducers属性来进行修改操作
- 在组件中使用dispatch调用reudcer修改操作
- 开始在reducers中进行修改
- 传递修改数据
- reducers中接收数据并修改
- dva-effects
- 知识点扩展---ES6 Generator
- Generator主要是异步编程,用来封装一个异步任务,是一个异步任务的容器
- 特点:交出普通函数的执行权(可以让函数在调用时按照我们的需要执行或暂停)
- 普通函数:在调用时函数中的内容会全部执行
- generator函数可以 让函数体内的内容随着我们的需要走走停停
- 在声明函数的function关键字和函数名之间有一个*号(用于区别普通函数)
- yield(是异步不同阶段的分割线)在generator函数体内进行使用,可以定义不同的内部状态
- 使用next()来执行generator函数
- generator函数:
- 运行后发现函数不会执行
- 调用generator函数时会返回一个generator对象
- 使用next()方法来执行generator函数
- 知识点扩展---ES6 Generator传参
- dva-基本异步请求
- dva-model异步请求
- dva-model异步请求修改state
- dva-subscription订阅
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 = {width: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规则扩展
- html的value属性要写成:defaultValue
- html的checked属性要写成:defaultChecked
- style 里面写对象
- 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三种用法
-
字符串
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> ) } }
-
回调函数
回调函数就是在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> ) } }
-
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指向
-
通过bind方法进行原地绑定,从而改变this指向
-
通过创建箭头函数
-
在constructor中提前对事件进行绑定
-
将事件调用的写法改为箭头函数的形式
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样式文件夹
创建组件
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
解决方式卸载掉原有的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路由配置文件
约定式路由---动态路由
-
约定[]包裹的文件或文件夹为动态路由
-
接受
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 **渲染子组件。
-
必须在父路由中加入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)
-
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('改变了') } } }