【React】React 详细教程
前言1、react与vue的对比1.1、什么是模块化是从代码的角度来进行分析的把一些可复用的代码抽离为单独的模块;便于项目的维护和开发1.2、什么是组件化是从UI界面角度来进行分析的把一些可复用的UI元素抽离为单独的组件1.3、组件化的好处随着项目规模的增大,手里的组件就越来越多;很方便就能把现有的组件拼接为一个完整的页面1.4、vue中是如何实现组件化的通过.vue文件来创建对应的组件templ
前言
1、react与vue的对比
1.1、什么是模块化
是从代码的角度来进行分析的
把一些可复用的代码抽离为单独的模块;便于项目的维护和开发
1.2、什么是组件化
是从UI界面角度来进行分析的
把一些可复用的UI元素抽离为单独的组件
1.3、组件化的好处
随着项目规模的增大,手里的组件就越来越多;
很方便就能把现有的组件拼接为一个完整的页面
1.4、vue中是如何实现组件化的
通过.vue文件来创建对应的组件
template 结构 script 行为 style 样式
1.5、React如何实现组件化:
React中有组件化的概念,但是并没有像.vue这样的组件模板文件
React中,一切都是以JS来表现的
2、虚拟DOM
2.1、DOM的本质是什么
浏览器中的概念,用js对象来表示页面上的元素,并提供了操作dom对象的api
2.2、什么是React中的虚拟DOM
框架中的概念:用js对象来模拟页面上的DOM和DOM嵌套
2.3、为什么要实现虚拟DOM(虚拟DOM的目的)
为了实现页面中,DOM元素的搞笑更新
一、react简介
1、什么是react
- react 是一个将数据渲染为HTML视图的开源JavaScript库
2、为什么要学react
- 原生JavaScript操作DOM繁琐效率低(DOM-API操作UI)
- 使用JavaScript直接操作DOM,浏览器会进行大量的重绘重排
- 原生JavaScript没有组件化编码方案,代码复用率低
3、react的特点
- 采用组件化模式、声名式编码,提高开发效率及组件复用率
- 命令式编码:命令机器如何去做事情,说一步做一步
- 声名式编码:告诉机器想要什么,机器去想如何去做
- 在 React Native 中可以使用 React 语法进行移动端开发。
- 使用虚拟 DOM+优秀的 Diffing 算法,尽量减少与真实 DOM 的交互
二、react初体验
1、 所需依赖
// crossorigin 没有这个属性的话浏览器会隐藏跨域页面的报错问题
// 加上这个属性可以使浏览器获取到引入脚本中的具体报错
// babel.js: ES6转ES5,jsx转js
// react.development.js:react核心库 引入之后全局多了React对象
// react-dom.development.js:react扩展库(帮助操作dom)引入之后全局多了ReactDOM对象
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
2、使用 jsx
- 全称:JavaScript XML
- react 定义的一种类似于 XML 的 js 扩展语法:js+XML
- jsx 是原始 js 创建虚拟 DOM(React.creatElement())的语法糖
- 作用:用来简化创建虚拟 DOM
- 不是字符串,也不是 HTML/XML 标签
- 最终生成的就是一个 js 对象
- jsx 语法规则
- 定义虚拟 DOM 时,不要写引号
- 标签中混入 JS 表达式时要使用 {}
- JS 表达式:一个表达式产生一个值(即有返回值),可放在任何一个需要值的地方
// 1、变量 a // 2、运算表达式 a+b // 3、函数调用表达式 函数默认返回undefined demo(1) // 4、数组的map方法 有返回值 arr.map() // 5、定义一个函数 返回函数本身 function test () {}
- JS语句:js代码块 都没有返回值,只是控制代码走向
if(){} for(){} switch(){case:xxx}
- JS 表达式:一个表达式产生一个值(即有返回值),可放在任何一个需要值的地方
- 样式的类名指定不要用 class,要用 className(为了与es6类区分)
- 内联样式,要用 style={{key:value}} 的形式来写
- 虚拟 DOM 只有一个根标签
- 标签必须闭合
- 标签首字母
- 若大写字母开头,react 就会去渲染对应的组件,若组件没定义,则报错
- 若小写字母开头,则将该标签转为 html 中同名标签,若没有对应标签则报错
- <script type="text/babel"></script>
- 可以使创建虚拟 dom 节点更加的方便
3、虚拟DOM
- 本质是 Object 类型的对象(一般对象)
- 真实 DOM 比虚拟 DOM 重,虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多属性
- 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
4、模块与组件、模块化与组件化的理解
4.1、js模块(只是对js进行拆分)
- 理解:
- 向外提供特定功能的js程序,一般就是一个js文件
- 为什么要拆成模块:
- 随着业务逻辑增加,代码越来越多且复杂
- 作用:
- 复用js,简化js的编写,提高js运行效率
4.2、组件()
- 理解:
- 用来实现局部功能效果的代码和资源的集合( html/css/js/image 等等 )
- 为什么:
- 一个界面的功能更复杂
- 作用:
- 复用编码,简化项目编码,提高运行效率
4.3、模块化
当应用的js都以模块来编写的,这个应用就是一个模块化的应用
4.4、组件化
当应用是以多组件的方式实现,这个应用就是一个组件化应用
三、React面向组件编程
1、组件详解
1.1、函数式组件
- 函数定义的组件(适用于简单组件的定义)
<script type="text/babel"> function Demo() { // babel翻译之后打开严格模式,this不能指向window,变为undefined console.log("this", this); return <h1>函数组件</h1>; } ReactDOM.render(<Demo />, document.getElementById("app")); // 执行了ReactDOM.render之后发生了什么? // 1、React解析组件标签,找到了Demo组件 // 2、发现组件是使用函数定义的,随后调用该函数 // 3、将返回的虚拟DOM转为真实DOM,随后呈现在页面中 </script>
1.2、类组件
- 使用es6 class 类定义的组件(适用于复杂组件)
<script type="text/babel"> // 使用类组件一定要继承React.Component class MyComponent extends React.Component { // 构造器不是必须的 // 但一定要有render并且有返回值 render() { // 虽然我们没有手动new MyComponent()进行实例化,但是ReactDOM.render帮我们做了实例化操作 // 所以 此处 render存在于MyComponent的原型对象上,供实例使用 // 此处 render中的this指向MyComponent的实例对象 console.log("render中的this", this); // 指向MyComponent的实例对象 MyComponent{...} return <h1>我是用类定义的组件(使用与【复杂组件】的定义)</h1>; } } // 渲染组件到页面; ReactDOM.render(<MyComponent />, document.getElementById("app")); //执行了ReactDOM.render(<MyComponent />, ...))之后,发生了什么? // 1、React解析组件标签,找到了MyComponent组件 // 2、发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法 // 3、将render返回的虚拟DOM转为真实DOM,随后呈现在页面中 </script>
2、组件实例的三大特性(类组件/ hooks)
2.1、state
2.1.1、理解
- state 是组件对象最重要的属性,值是对象
- 组件被称为“状态机”,通过更新组件的 state 来更新对应的页面显示(重新渲染组件)
2.1.2、强烈注意
- 组件中 render 方法中的this为组件实例对象
- 组件自定义的方法中 this 为 undefined,如何解决?
- 强制绑定 this ;通过函数对象的 bind()
- 箭头函数
- 状态数据,不能直接修改或更新
<script type="text/babel">
// 创建组件
class MyComponent extends React.Component {
// 构造器调用几次? 1次
constructor(props) {
super(props);
// 初始化状态
this.state = {
isHot: false,
};
// this指向的解决办法
// 左边的this.changeWeather是当前类的changeWeather变量
// 右边的this.changeWeather是类原型对象上的方法,将其this指向到当前类
this.changeWeather = this.changeWeather.bind(this);
}
// render调用几次? 1+n次,1是初始化,n是状态更新的次数
render() {
// render存在于MyComponent类的原型对象上
// render由MyComponent的实例调用,this指向MyComponent的实例对象
// 读取状态
const { isHot } = this.state;
console.log("render中的this", this); // 指向MyComponent的实例对象 MyComponent{...}
// onClick 不是html dom事件onclick 不能与原生事件相比较
// 此处 实际上是将实例上的changeWeather方法赋值给onClick
// 然后通过onClick去直接调用changeWeather事件,此时this指向就不是实例了
return (
<h1 onClick={this.changeWeather}>
今天的天气{isHot ? "炎热" : "凉爽"}
</h1>
);
}
// changeWeather调用几次? 点几次调几次
changeWeather() {
// changeWeather 是在实例的原型对象上,供实例使用
// 由于changeWeather是作为onClick的回调,所以不是通过实例调用的,是直接调用
// 类中的方法默认开启局部的严格模式,所以chagneWeather中的this为undefined
console.log("changeWeather", this);
// 严重注意:状态必须通过setState进行更新,且更新是一种合并,不是替换
// setState是合并的操作不会影响state中其他属性,不是替换
this.setState({ isHot: !this.state.isHot });
}
}
// 渲染组件到页面;
ReactDOM.render(<MyComponent />, document.getElementById("app"));
// ReactDOM.render都做了写什么;
// 1、先判断传入的组件标签
// 是小写就对应html中的标签进行渲染
// 是大写就判断是否是函数组件或类组件
// 是函数组件就找到对应的函数组件进行渲染
// 是类组件就先实例化,然后使实例化调用原型上的render方法渲染
// 将返回的虚拟dom树转换为真实dom数
</script>
2、props
2.1、类组件接收props值
<script type="text/babel">
class Person extends React.Component {
// 构造器作用只有两个
// 1、初始化数据状态(可以简写)
// 2、给事件绑定this对象(可以用赋值语句+箭头函数来简写)
// 结论:能省略就省略
constructor(props) {
// 构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
super(props);
console.log("props", props);
// super中传递props之后可以用this.props访问props
console.log("this.props", this.props);
}
render() {
const { name, age, sex } = this.props;
return (
<ul>
<li>{name}</li>
<li>{sex}</li>
<li>{age + 1}</li>
</ul>
);
}
}
let data = { name: "cz", age: "18", sex: "男" };
// js中属性对象是不能直接展开的,但是在react中有babel和react核心库对其进行了处理,所以这里可以直接用展开符将对象展开
ReactDOM.render(<Person {...data} />, document.getElementById("app"));
ReactDOM.render(
<Person name="xp" age="16" sex="女" />,
document.getElementById("app")
);
</script>
2.2、函数组件接收props值
<script type="text/babel">
function Person(props) {
const { name, sex, age } = props;
return (
<ul>
<li>{name}</li>
<li>{sex}</li>
<li>{age + 1}</li>
</ul>
);
}
let data = { name: "cz", age: 18, sex: "男" };
// js中对象是不能直接展开的,但是在react中有babel和react核心库对其进行了处理,所以这里可以直接用展开符将对象展开
ReactDOM.render(<Person {...data} />, document.getElementById("app1"));
ReactDOM.render(
<Person name="xp" age={16} sex="女" />,
document.getElementById("app2")
);
</script>
2.3、refs (不要过度使用 ref )
- 理解
- 组件内的标签可以通过 ref 来标识自己(可替代id)
- 写法
- 字符串写法
//字符串写法 不推荐(过时,未来会移除) //字符串写法会产生一些问题(效率不高) <input ref="input1" type="text" placeholder="请输入" /> // 获取 this.refs.input1
- 回调函数写法
- 第一种内联方式
<script type="text/babel"> class Demo extends React.Component { showData = () => { console.log(this); // 如果写的refs的回调写法,则node节点直接赋值给了实例的input1变量上 // 就不能再通过refs上取了 const { input1 } = this.refs; // 直接在实例上取 const { input1 } = this; alert(input1.value); }; showData2 = () => { const { input2 } = this; alert(input2.value); }; render() { return ( <div> {/* 1、执行render函数之后会执行render函数中的jsx语句 2、遇到jsx中ref为回调写法则会触发里边的回调 (特定标签的回调才会被react处理,随便写个标签的表达式会被处理,但回调不会,而且还会报错 例如:xxx={()=>{...}})错误 3、在此处会触发一个箭头函数 4、运行箭头函数则将箭头函数中的参数(这个参数就是当前节点由react处理)赋值给this.input1 5、因为是箭头函数本身没有this,会向上找到render的this 即当前实例 6、所以此处是将当前node节点赋值给实例的input1变量上 */} <input ref={(currentNode) => { this.input1 = currentNode; }} type="text" placeholder="点击按钮提示数据" /> <button style={{ margin: "0 10px" }} onClick={this.showData}> 点击提示左侧输入框内容 </button> {/* 此处为refs回调写法的简写方式 */} <input onBlur={this.showData2} ref={(c) => (this.input2 = c)} type="text" placeholder="失去焦点提示数据" /> </div> ); } } ReactDOM.render(<Demo />, document.getElementById("app1")); </script>
- 第二种类绑定方法
<script type="text/babel"> class Demo extends React.Component { state = { isHot: true }; showInfo = () => { const { input1 } = this; alert(input1.value); }; changeWeather = () => { const { isHot } = this.state; this.setState({ isHot: !isHot, }); }; saveInput = (currentNode) => { console.log("currentNode", currentNode); }; render() { const { isHot } = this.state; return ( <div> <h2>今天天气很{isHot ? "炎热" : "凉爽"}</h2> {/* 1、此处为内联写法(直接将执行的代码放在回调函数里的方式) 2、这样的写法会导致 每次更新(除去第一次render)render时,会重新加载当前函数 会执行两次,第一次会把参数赋值为null,第二次才会将当前dom节点绑定上 3、对实际代码不会产生影响,依旧可以使用 */} <input ref={(currentNode) => { this.input1 = currentNode; console.log("我被调用了", currentNode); }} type="text" /> <button onClick={this.showInfo}>点击提示输入的数据</button> <button onClick={this.changeWeather}>点击我改变天气</button> {/* 此处为类绑定回调 此方法可以避免ref加载两次的问题 */} <input ref={this.saveInput} type="text" /> </div> ); } } ReactDOM.render(<Demo />, document.getElementById("app1")); </script>
- 第三种 createRef (最推荐的方式)
<script type="text/babel"> class Demo extends React.Component { // React.createRef调用后可以返回一个容器 // 该容器可以存储被ref所表示的节点 // 该容器为专人专用,只能存一个,后边的会覆盖当前的 myRef = React.createRef(); showData = () => { console.log("myRef", this.myRef); // {current: input} }; render() { return ( <div> <input ref={this.myRef} type="text" placeholder="点击按钮提示数据" /> <button style={{ margin: "0 10px" }} onClick={this.showData}> 点击提示左侧输入框内容 </button> </div> ); } } ReactDOM.render(<Demo />, document.getElementById("app1")); </script>
- 第一种内联方式
- 事件处理
- 通过 onXxx 属性指定事件处理函数(注意大小写)
- React 使用的是自定义(合成)事件,而不是使用的原生 DOM 事件 -----为了更好的兼容性
- React 中的事件是通过事件委托方式处理的(委托给组件最外层的元素)----为了高效
- 通过 event.target 得到发生时间的 DOM 元素对象----不要过度使用ref
- 当发生事件的元素刚好是我们需要获取的元素就可以不用写 ref 了
- 通过 onXxx 属性指定事件处理函数(注意大小写)
- 字符串写法
3、收集表单数据
- 非受控组件
- 受控组件
- 表单里所有输入类的 dom 在输入的同时能够拿到输入的值
4、高阶函数、柯里化
4.1、高阶函数
- 如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
- 若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数
- 若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数
- 常见的高阶函数
- promise
- setTimeout
- 数组常见方法 map reduce forEach
4.2、函数柯里化
- 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
5、生命周期
5.1、理解
- 组件从创建到死亡他会经历一些特定的阶段
- React 组件中包含一系列狗子函数生命周期回调函数,会在特定时刻调用
- 我们在定义组件时,会在特定的生命周期回调函数中做特定的工作
5.2、生命周期流程(旧)
- 初始化阶段:由 ReactDOM.render() 触发-----初次渲染
- constructor
- componentWillMount()
- render()
- componentDidMount() -----常用
- 一般在这个钩子中做初始化的事
- 开启定时器
- 发送网络请求
- 订阅消息
- 一般在这个钩子中做初始化的事
- 更新阶段:由组件内部 this.setState() 或父组件 render 触发
- shouldComponentUpdate()
- componentWillUpdate()
- render()
- componentDidUpdate()
- 卸载组件:由 ReactDOM.unmountComponentAtNode() 触发
- componentWillUnmount() -----常用
- 一般在这个钩子中做一些收尾的事
- 关闭定时器
- 取消订阅消息
- 一般在这个钩子中做一些收尾的事
- componentWillUnmount() -----常用
- 组件接收新的 props 钩子 (第一次接收的不算)
- componentWillReceiveProps()
5.3、生命周期(新)
- 所有带 will 的钩子除了即将卸载的钩子都要加 UNSAFE_ 前缀
- 这些还是之前的钩子,以后即将废弃,增加它们现在的使用成本
- componentWillMount() ----> UNSAFE_componentWillMount()
- componentWillUpdate() ----> UNSAFE_componentWillUpdate()
- componentWillReceiveProps() ----> UNSAFE_componentWillReceiveProps()
- 增加了2个新的钩子
- getDerivedStataeFromProps
- getSnapshotBeforeUpdate
- 初始化阶段:由 ReactDOM.render() 触发 ---- 初次渲染
- constructor()
- getDerivedStateromProps()
- render()
- componentDidMount() ===> 常用
- 一般在这个钩子中做一些初始化的事情
- 开启定时器
- 发送网络请求
- 订阅消息
- 一般在这个钩子中做一些初始化的事情
- 更新阶段:由组件内部 this.setState() 或父组件重新render触发
- getDerivedStateFromProps()
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate()
- componentDidUpdate()
- 卸载组件:由 React.unmountComponentAtNode() 触发
- componentWillUnmount() ====》 常用
- 一般在这个钩子中做一些首尾的事情
- 关闭定时器
- 取消订阅消息
- 一般在这个钩子中做一些首尾的事情
- componentWillUnmount() ====》 常用
6、diff算法
7、React 发送网络请求
- 跨域
三、React路由
1、SPA的理解
- 单页Web应用(single page web application,SPA)。
- 整个应用只有一个完整的页面。
- 点击页面中的链接不会刷新页面,只会做页面的局部更新
- 数据都需要通过ajax请求获取, 并在前端异步展现。
2、路由的理解
- 什么是路由?
- 一个路由就是一个映射关系( key:value )
- key 为路径, value 可能是 function 或 component
- 路由分类
- 后端路由
- 理解: value 是 function , 用来处理客户端提交的请求。、
- 注册路由: router.get(path, function(req, res))
- 工作过程:当 node 接收到一个请求时, 根据请求路径找到匹配的路由, 调用路由中的函数来处理请求, 返回响应数据
- 前端路由
- 浏览器端路由,value 是 component,用于展示页面内容。
- 注册路由:
- 工作过程:当浏览器的 path 变为 /test 时, 当前路由组件就会变为 Test 组件
- 后端路由
3、路由的基本使用
- 明确好界面中的导航区,展示区
- 导航区的a标签改为Link标签
// to后边写的为组件对应的域名后想要显示的名字
<Link to='/Demo'>Demo</Link>
- 展示区写Route标签进行路径的匹配
// 要先引入Demo组件
import Demo from './components/demo'
// 只要跟link中的to后的path对应上就渲染
<Route path='/Demo' component={Demo}>
- 的最外侧包裹了一个或标签
- 所有的路由只能被同一个路由标签包裹才能起到匹配渲染的作用
4、路由组件与一般组件
- 写法不同
- 一般组件
- 可以通过 export default withRouter(组件名) 方式修改为路由组件
- 路由组件:
- 一般组件
- 存放位置不同
- 一般组件: components
- 路由组件: pages
- 接受props不同
- 一般组件:写组件时传递什么接收什么
- 路由组件:接收到路由信息 有三个固定属性
- history
- location
- match
5、NavLink 与 封装NavLink
- NavLink可以实现路由链接的高亮,通过activeClassName属性来指定高亮样式名
- 标签体内容是一个特殊的标签属性
- 通过this.props.children可以获取标签体内容
6、switch的使用
- 通常情况写,path 和 component 是一一对应的关系
- Switch标签包裹Route 标签可以提高路由匹配效率(匹配上一个就不往下进行了)
7、解决多级路径刷新页面样式丢失的问题
- publich/index.html 中 引入样式时 路径 ./ 修改为 / (常用)
- publich/index.html 中 引入样式时不写 ./ 写 %PUBLIC_URL% (常用)(react脚手架里)
- 使用HashRouter
8、路由的严格匹配与模糊匹配
- 默认使用的是模糊匹配(简单记:【输入的路径】必须包含要【匹配的路径】且顺序要一致)
- 开启严格匹配:
- 严格模式不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
9、Redirect的使用
- 一般所有的路由注册的最下方,当所有路由都无法匹配时,跳转到Redirect指定的路由
- 具体编码:
<Switch>
<Route path="/cz/home" component={Home}></Route>
<Route path="/about" component={About}></Route>
<Redirect to="/cz/home" />
</Switch>
10、嵌套路由
- 注册子路由是要写上副路由的path值
- 路由的匹配是按照注册路由的顺序进行的
11、向路由组件传递参数
- params参数
// 路由链接(携带参数)
<Link to='/detail/03/cz'>详情</Link>
// 注册路由(声明接收)
<Route path="/detail/:id/:name"></Route>
// 接收参数
const {id,name}= this.props.match.params
12、search参数
// 路由链接(携带参数)
<Link to="/home/news?id=03&name=cz">news</Link>
// 注册路由(无需声明接收)
<Route path="/home/news" component={News}></Route>
// 接收参数(获取到的为urlencoded编码字符串,可通过qs.parse解析)
const {search} = this.props.location
const searchQuery = qs.parse(search.slice(1))
13、state参数
// 路由链接(携带参数)
<Link to={{pathname:'/home/detail',state:{id:'03',name:'cz'}}}>news</Link>
// 注册路由(无需声明接收)
<Route path="/home/detail" component={Detail}></Route>
// 接收参数(获取到的为urlencoded编码字符串,可通过qs.parse解析)
const {state} = this.props.location
14、编程式路由导航
- 借助this.props.history对象上的API对操作路由跳转、前进、后退
this.props.history.push()
this.props.history.replace()
this.props.history.goBack()
this.props.history.goForward()
this.props.history.go()
15、BrowserRouter 与 HashRouter 的区别
- 底层原理不一样:
- BrowserRouter使用的是H5的 history API,不兼容IE9以下的版本
- HashRouter使用的是URL的哈希值
- url 表现形式不一样
- BrowserRouter 的路径中没有#
- HashRouter 使用的是URL的哈希值
- 刷新后对路由state参数的影响
- BrowserRouter 没有任何影响,因为state保存在history对象中
- HashRouter 刷新后会导致路由 state 参数的丢失!!!
- 备注:HashRouter 可以用于解决一些路径错误相关的问题
四、Redux
1、Redux是什么
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用。
- 作用: 集中式管理react应用中多个组件共享的状态。
2、什么情况下需要使用redux
- 某个组件的状态,需要让其他组件可以随时拿到(共享)。
- 一个组件需要改变另一个组件的状态(通信)。
- 总体原则:能不用就不用, 如果不用比较吃力才考虑使用。
3、redux工作流程
4、redux的三个概念
4.1、action
- 动作的对象
- 包含2个属性
- type:标识属性,值为字符串,唯一,必要属性
- data:数据属性,值类型任意,可选属性
- 例子
{ type:'add', data:{name:'cz',age:18} }
4.2、reducer
- 用于初始化状态,加工状态
- 加工时,根据旧的 state 和 action ,产生新的 state 的纯函数
- 纯函数:
- 只要有同样的输入,必定得到同样的输出
- 必须遵守以下约束
- 不能改写参数数据
- 不会产生任何副作用,例如网络请求,输入和输出设备
- 不能调用 Date.now() 或者 Math.random() 等不纯的方法
- 为什么 reducer 要是一个纯函数
- 首先 reducer 被设计出来是为了接受一个旧 state 和 action 返回一个新的 state 的过程
- 如果使用不纯的函数,那么返回的 state 将无法被保障
- 比如 value=getValue+1;getValue 是一个请求,如果清楚出现错误,那么返回的state 将变得无法确定,前端拿到的数据也不确定,就失去了作为 reducer 的价值
4.3、store
- 将 state、action、reducer 联系在一起的对象
5、 redux使用
5.1、去除组件自身的状态
5.2、创建store.js
- 引入redux中的createStore函数,创建一个store
- createStore调用时要传入一个为起服务的reducer
- 暴露store对象
- 创建reducer.js
- reducer的本质是一个函数,接受preState,action两个参数,返回加工后的状态
- reducer有两个作用:
- 初始化状态
- 加工状态
- reducer第一次被调用时,是store自动触发的,传递的preState是undefined
- 在入口文件js中检测store中状态的改变,一旦发生改变重新渲染
- 备注:redux只负责管理状态,至于状态的改变驱动页面的展示,要靠我们自己写
import store from '../redux/store'
// 监听store状态变化
store.subscribe(()=>{ReactDOM.render(<App />,document.getElementById('root'))})
6、redux 异步使用
- 明确延迟的动作不想交给组件自身,想交给action去处理
- 何时需要异步action
- 想要对状态进行操作,但是具体的数据靠异步任务返回
- 创建action的函数不再返回一个一般对象,而是返回一个函数,在该函数中写异步任务
- 异步任务有结果后,分发一个同步的action去真正操作数据
- 备注:异步action不是必须要写的,完全可以自己等待异步任务的结果再去分发action。
- 具体步骤
- 需要在store页面中引入applyMiddleware方法去解析中间件
- 下载并引入react-thunk中间件去解析action返回的函数
store.js
// 该问见专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,用来创建redux中最为核心的store对象
// 引入 applyMiddleware ,用来处理中间件
import { createStore,applyMiddleware } from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer.js'
// 引入可以处理action为函数的异步方法的中间件redux-thunk
import thunk from 'redux-thunk'
export default createStore(countReducer,applyMiddleware(thunk))
action.js
// action为一个方法时,为异步, store不认识为函数的action,需要用中间件去处理
export const actionAsyncAdd = (data,time) => {
// store会在调用该方法时将dispath传入
return (dispatch)=>{
setTimeout(()=>{
dispatch(actionAdd(data))
},time)
}
}
7、react-redux 使用
7.1、原理图
7.2、react-redux使用
- 创建store 同 Redux 方式一样,全局有且只有一个store
- 在容器外部用包裹并传入store
- 传入的store可以在connect中传入的第一个函数内获取到
- 引入react-redux中的connect方法包装子组件形成父子关系,并暴露
export default connect( state => ({ count: state.count }), // 接收store中的数据 { jia: ()=>{}, // 传入操作数据状态的方法 jian: ()=>{}, })(Count); // 将子组件传入
- 子组件中通过 props 来获取和操作 connect 传递的数据和方法
- react-redux 优化
- 容器组件和UI组件整合成一个文件
- 无需自己给容器组件传递 store,给包裹一个即可
- 使用了 react-redux 后不用自己检测 redux 中状态的改变,容器组件可以自动完成这个工作
- mapDispatchToProps 也可以简写成一个对象
- 一个组件要和redux“打交道”需要经过哪几步?
- 定义好UI组件
- 引入 connect 生成一个容器组件并暴露,写法如下
connect( state=>({key:value}) //映射状态 {key:xxxxAction} //映射的操作状态的方法 )(UI组件)
-
在UI组件中通过this.props.xxxx 读取和操作状态
8、reducer 合并 数据共享
// 该问见专门用于暴露一个store对象,整个应用只有一个store对象
// 引入createStore,用来创建redux中最为核心的store对象
// 引入 applyMiddleware ,用来处理中间件
// 引入 combineReducers,用来合并reducer
import { createStore,applyMiddleware,combineReducers } from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer.js'
// 引入为person组件服务的reducer
import personReducer from './person_reducer.js'
const allReducers = combineReducers({
count:countReducer,
personReducer:personReducer
})
// 引入可以处理action为函数的异步方法的中间件redux-thunk
import thunk from 'redux-thunk'
// 将合并后的reducers交给store
export default createStore(allReducers,applyMiddleware(thunk))
- 定义一Person组件,和 Count 组件通过 redux 共享数据
- 为 Person 组件编写 reducer,action
- 重点:Person 的 reducer 和 Count 的 reducer 要使用 combineReducers 进行合并,合并后的总状态是一个对象!!!
- 交给 store 的是总 reducer,最后注意在组件中取出状态的时候,记得“取到位”
五、react扩展
1、setState
1.1、第一种写法:setState( {key:value} , [callback] ) ---对象式写法
- setState是个同步方法,但是setState调用后引起的react状态更新是异步的
- setState第二个可选的回调参数
- 它在状态更新完毕、界面也更新后(render调用后)才被调用
1.2、第二种写法: setState( updater, [callback] ) -----函数式写法
- updater为一个返回一个更改对象的函数
- updater可以接受到 state 和 props 两个参数
- callback是可选的回调函数,它在状态更新,界面也更新后(render调用后)才被调用
- 什么时候表现“同步”什么时候表现“异步”
- setState内部有合并修改的策略,为的就是优化性能,这一策略导致setState之后渲染会出现异步的情况
- 所以当我们使用react可以控制的api的时候,都将体现出“异步”的情况
- react 生命周期,react事件,等等
- 当我们使用react无法控制的api就可以脱离setState合并修改的操作,使setState变为同步操作
- setTimeout , promise.then,ajax,绑定原生事件
2、lazyLoad
2.1、路由组件的lazyLoad
//1.通过React的lazy函数配合import()函数动态加载路由组件 ===> 路由组件代码会被分开打包
const Login = lazy(()=>import('@/pages/Login'))
//2.通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
<Suspense fallback={<h1>loading.....</h1>}>
<Switch>
<Route path="/xxx" component={Xxxx}/>
<Redirect to="/login"/>
</Switch>
</Suspense>
3、Hooks
3.1、React Hook/Hooks是什么?
- Hook是React 16.8.0版本增加的新特性/新语法
- 可以让你在函数组件中使用 state 以及其他的 React 特性
3.2、 三个常用的Hook
- State Hook: React.useState()
- Effect Hook: React.useEffect()
- Ref Hook: React.useRef()
3.3、 State Hook
- State Hook让函数组件也可以有state状态, 并进行状态数据的读写操作
- 语法: const [xxx, setXxx] = React.useState(initValue)
- useState() 说明:
- 参数: 第一次初始化指定的值在内部作缓存
- 返回值: 包含2个元素的数组, 第1个为内部当前状态值, 第2个为更新状态值的函数
- setXxx()2种写法:
- setXxx(newValue): 参数为非函数值, 直接指定新的状态值, 内部用其覆盖原来的状态值
- setXxx(value => newValue): 参数为函数, 接收原本的状态值, 返回新的状态值, 内部用其覆盖原来的状态值
3.4、Effect Hook
- Effect Hook 可以让你在函数组件中执行副作用操作(用于模拟类组件中的生命周期钩子)
- React中的副作用操作:
- 发ajax请求数据获取
- 设置订阅 / 启动定时器
- 手动更改真实DOM
- 语法和说明:
useEffect(() => { // 在此可以执行任何带副作用操作 return () => { // 在组件卸载前执行 // 在此做一些收尾工作, 比如清除定时器/取消订阅等 } }, [stateValue]) // 如果指定的是[], 回调函数只会在第一次render()后执行
- 可以把 useEffect Hook 看做如下三个函数的组合
- componentDidMount()
- componentDidUpdate()
- componentWillUnmount()
3.5、Ref Hook
- Ref Hook可以在函数组件中存储/查找组件内的标签或任意其它数据
- 语法: const refContainer = useRef()
- 作用:保存标签对象,功能与React.createRef()一样
4、Fragment
使用:
<Fragment><Fragment>
<></>
作用: 可以不用必须有一个真实的DOM根标签了
5、Context
理解:
> 一种组件间通信方式, 常用于【祖组件】与【后代组件】间通信
使用
- 创建Context容器对象:
- const XxxContext = React.createContext()
- 渲染子组时,外面包裹 xxxContext.Provider , 通过 value 属性给后代组件传递数据:
<xxxContext.Provider value={数据}> 子组件 </xxxContext.Provider>
- 后代组件读取数据:
- 第一种方式:仅适用于类组件
- static contextType = xxxContext // 声明接收context
- this.context // 读取context中的value数据
- 第二种方式: 函数组件与类组件都可以
<xxxContext.Consumer> { value => ( // value就是context中的value数据 要显示的内容 ) } </xxxContext.Consumer>
- 第一种方式:仅适用于类组件
注意
在应用开发中一般不用context, 一般都用它的封装react插件
6、组件优化
6.1、Component的2个问题
- 只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
- 只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
6.2、效率高的做法
只有当组件的state或props数据发生改变时才重新render()
原因:
Component中的shouldComponentUpdate()总是返回true
解决:
办法1:
重写 shouldComponentUpdate() 方法
比较新旧 state 或 props 数据, 如果有变化才返回 true , 如果没有返回 false
办法2:
使用 PureComponent
PureComponent 重写了 shouldComponentUpdate() , 只有 state 或 props 数据有变化才返回 true
注意:
只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
不要直接修改state数据, 而是要产生新数据
项目中一般使用PureComponent来优化
7、组件通信方式总结
7.1、组件间的关系:
- 父子组件
- 兄弟组件(非嵌套组件)
- 祖孙组件(跨级组件)
7.2、几种通信方式:
- props:
- children props
- render props
- 消息订阅-发布:
- pubs-sub、event等等
- 集中式管理:
- redux、dva等等
- conText:
- 生产者-消费者模式
7.3、 比较好的搭配方式:
- 父子组件:props
- 兄弟组件:消息订阅-发布、集中式管理
- 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多)
更多推荐
所有评论(0)