第一节:环境与项目搭建
一. 本地安装node
安装好node后全局安装webpack或yarn
# npm i -g webpack / npm i -g yarn
二. 安装官方create-react-app脚手架并搭建项目
1.全局安装命令
# npm i -g create-react-app
或
# yarn add -g create-react-app
2.安装好后查看版本
# create-react-app --version
3.开始创建项目
# create-react-app demo
创建项目的同时会帮你自动下载依赖,所以不用自己npm install安装依赖,创建完成后直接cd demo去到当前项目npm start 或 yarn start
第二节:基础知识
一. 讲解state变量渲染和setState修改数据
1.在组件里面我们通过{}在JSX里面渲染变量
2.如果数据需要修改,并且需要页面同时响应改变,那就需要把变量放在state里面,同时使用setState修改
3.初始化状态state
//初始化状态state
this.state = {
count: 0
}
4.更新状态使用setState,不能直接this.state.count = xxx 【区别于Vue】
//更新状态使用setState
this.setState({
count: this.state.count + 1
})
注意事项:
①setState是异步的,底层设计同一个生命周期会批量操作更新state
②setState第二个参数是一个可选参数,传入一个回调函数可以获取到最新的state
③当修改的state依赖上一次修改的state的值时,可以使用以下这种方法修改:
this.setState((prevState, prevProps) => ({
//prevState:上一次修改的状态state
//prevProps: 上一次修改的属性props
count: prevState.count + 1
}), () => {
//这里可以获取到最新的state
console.log(this.state.count)
})
二. props属性传递
父组件向子组件传递属性利用props接收,使用例子:
- 父组件传入参数
import ParentDemo from './ParentDemo'
<ParentDemo title='父组件title信息'></ParentDemo>
- 子组件使用
1. class组件使用
import React from 'react'
export default class ChildDemo extends React.Component {
render() {
return (
<h3>{this.props.title}</h3>
)
}
}
2.函数组件使用
import React from 'react'
export default function ChildDemo(props) {
return (
<h3>{props.title}</h3>
)
}
或接收父组件的参数解构写法
export default function ChildDemo({title}) {
return (
<h3>{title}</h3>
)
}
三. 条件渲染与数据循环
1. 条件渲染写法,一般使用三目表达式
- 在render函数里使用三目运算:
let result = this.state.showTitle ? <h3>{this.props.title}</h1> : null
{result}
- 直接使用if else
let result
if(this.state.showTitle) {
result = {
<h3>this.props.title</h3>
}
} else {
result = null
}
{result}
2.数据循环渲染
import React from 'react'
export default class ListLoopDemo extends React.Component {
constructor(props) {
super(props)
this.state = {
lists: [
{name: '张无忌', age: 25},
{name: '张翠山', age: 35},
{name: '张三丰', age: 45},
{name: '张敏敏', age: 25},
]
}
}
render(
return (<div>
<ul>
{this.state.lists.map(item => {
return <li key={item.name}>
<span>{item.name}</span>
<span>{item.age}</span>
</li>
})}
</ul>
</div>)
)
}
四.事件监听
以点击事件为例,使用方法如下:
//小驼峰写法,事件名用{}包裹
<button onClick={}></button>
由于react的this指向问题,所以在事件绑定时要特别注意,一般使用的事件绑定写法有三种:
1. 第一种利用bing绑定,写法如下,这种比较少用
//在constructor里面利用bing绑定继承this,解决方法的this指向问题
constructor(props) {
super(props)
this.showTitleFun = this.showTitleFun.bind(this)
}
showTitleFun() {
//执行某些操作
this.setState({})
}
//在DOM元素上直接使用
<button onClick={this.showTitleFun}>不显示title</button>
2.第二种箭头函数写法
showTitleFun = () => {
//执行某些操作
this.setState({})
}
//在DOM元素上直接使用
<button onClick={this.showTitleFun}>不显示title</button>
3.第三种直接使用箭头函数返回一个函数
showTitleFun() {
//执行某些操作
this.setState({})
}
<button onClick={() => this.showTitleFun()}>不显示title</button>
五. React样式编写
1.行内样式编写
<img style={{ width: "100px", height: "100px" }} />
2.添加类名,区别html标签类名
<img className="img" />
3.添加属性
<img src={logo} />
六. React实现双向数据绑定
使用input为例的双向数据绑定要点:
1.动态绑定value属性
//在state里面定义一个绑定input的value属性
this.state = {
inputVal: 'me是input的初始值'
}
//然后在input里动态绑定上面定义的变量
<input type="text" value={this.state.inputVal} </input>
2.监听input的onChange事件
//定义onChange绑定的事件
inputValChange = e => {
this.setState({
text: e.target.value
})
}
//在input上绑定inputValChange到onChange上
<input type="text" value={this.state.inputVal} onChange={e => this.inputValChange(e)} />
七. React生命周期
1. componentWillMount 组件将要挂载
2. componentDidMount 组件已经挂载
3. componentWillReceiveProps 父组件传递的属性有变化,做相应响应
4. shouldComponentUpdate 组件是否需要更新,返回布尔值,true则当前组件更新,false当前组件不更新;优化点
5. componentWillUpdate 组件将要更新
6. componentDidUpdate 组件已经更新
7. componentWillUnmount 组件已经销毁
第三节:React组件写法
一. 傻瓜组件和聪明组件区别
1.傻瓜组件也叫展示组件,负责根据props显示页面信息
2.聪明组件也叫容器组件,负责数据的数据、处理
3.分清楚展示组件和容器组件的优势
① 分离工作组件和展示组件
② 提高组件的重要性
③ 提高组件可用性和代码阅读
④ 便于测试于后续的维护
二.函数式组件
1. 函数式组件是一种无状态组件,是为了创建纯展示组件,这种组件只负责根据传入的props来展示,不涉及到要state状态的操作;
2. 组件不会被实例化,整体渲染性能得到提升;
3. 组件不能访问this对象;
4. 组件无法访问生命周期的方法;
5. 无状态组件只能访问输入的props,同样的props会得到同样的渲染结果,不会有副作用;
6. 官方文档说:在大部分React代码中,大多数组件被写成无状态的组件,通过简单组合可以构建成其他的组件等,这种通过多个简单然后合并成一个大应用的设计模式被提倡;
具体写法如下:
//这种写法是新建一个组件页面把组件暴露出去的写法
import React from 'react'
export default function xxx() {
return (
<div>我是函数式组件</div>
)
}
//这种写法是在页面内部创建组件不用给外部使用,只供页面内部使用
function xxx() {
return (
<div>我是函数式组件</div>
)
}
三.class类组件
1. React.createClass是react刚开始推荐的创建组件的方式,现在基本不会见到了;
2. React.Component是以ES6的形式来创建react的组件的,是React目前极为推荐的创建有状态的组件的方式
在里面我们可以写入我们的状态、生命周期、构造函数等
具体使用如下:
import React, { Component } from 'react'
export default class ConditionLoop extends Component {
render() {
return (
<div></div>
)
}
}
第四节:React组件化与UI库使用
一.引入ant-design组件库
1.安装ant-design(在项目demo目录下执行 ):
npm install antd --save
2.使用button组件为例:
import React, { Component } from 'react'
import Button from 'antd/lib/button'
import 'antd/dist/antd.css'
export default class AntDesign extends Component {
render() {
return (
<div>
<Button type='primary'>按钮</Button>
</div>
)
}
}
二. 配置ant-design按需加载
需要对antd进行配置按需加载,需要对create-react-app的默认配置进行自定义
更改我们的启动插件:
1. 引入react-app-rewired并修改package.json里的启动配置。由于新的react-app-rewired@2.0 版本的关系,还需要安装customize-cra
2. 安装命令 yarn add react-app-rewired customize-cra (如果还没安装yarn可以先执行npm install yarn -g进行安装)
3. 更改package.json文件
//package.json - 代表没更改前的代码, + 代表已经更改的代码
"script": {
- "start": "react-scripts start",
+ "start": "react-app-rewired start",
- "build": "react-scripts build",
+ "build": "react-app-rewired build",
- "test": "react-scripts test",
+ "test": "react-app-rewired test",
}
4. 然后在项目根目录创建一个 config-overrides.js 用于修改默认配置,先不写内容;
5. 执行安装 babel-plugin-import 插件(安装命令: yarn add babel-plugin-import )
6. 修改config-overrides.js文件内容如下:
const { override, fixBabelImports } = require('customize-cra')
module.exports = override(
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
);
到这里就成功了(修改文件记得重启项目才生效),可以移除上一节全量引入antd.css的代码了,然后更改引入组件方式
import { Button } from 'antd'
三. PureComponent性能优化
1.PureComponent是内部定制了shouldComponentUpdate生命周期的Component
① 它重写了一个方法来替换shouldComponentUpdate生命周期方法
2.平常开发过程中设计组件能使用PureComponent的地方都尽量使用
3.想要使用PureComponent特性要记住两个小原则:
① 确保数据类型是值类型
② 如果是引用类型,确保地址不变,同时不应当有深层次数据变化
4.使用PureComponent可以省去shouldComponentUpdate生命周期的代码,代码会简单很多
使用实例:
import React, { Component, PureComponent } from 'react'
//class Title extends Component {
class Title extends PureComponent {
/* shouldComponentUpdate(nextProps) {
return nextProps.title !== this.props.title
} */
render() {
console.log("我是title组件")
return (
<div>
标题:{this.props.title}
</div>
)
}
}
class Count extends Component {
render() {
console.log("我是条数组件")
return (
<div>
条目:{this.props.count}
</div>
)
}
}
export default class Pruememo extends Component {
constructor(props) {
super(props)
this.state = {
title: '这是标题',
count: 10,
}
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1,
})
}, 1000);
}
render(){
return (
<div>
<hr/>
<Title title={this.state.title}></Title>
<Count count={this.state.count}></Count>
</div>
)
}
}
四. React.memo性能优化
1. React.memo是一个高阶组件的写法
2. React.memo让函数组件也拥有了PrueComponent的功能
使用例子如下:
const MemoComponent = React.memo((props) => {
return (
<div>
<p>{props.xxx}</p>
<p>{props.xxx}</p>
</div>
)
});
完整代码如下:
import React, { Component, PureComponent } from 'react'
const Title = React.memo((props) => {
console.log('我是title组件')
return (
<div>
标题:{props.title}
</div>
)
});
class Count extends Component {
render() {
console.log("我是条数组件")
return (
<div>
条目:{this.props.count}
</div>
)
}
}
export default class Pruememo extends Component {
constructor(props) {
super(props)
this.state = {
title: '这是标题',
count: 10,
}
}
componentDidMount() {
setInterval(() => {
this.setState({
count: this.state.count + 1,
})
}, 1000);
}
render(){
return (
<div>
<hr/>
<Title title={this.state.title}></Title>
<Count count={this.state.count}></Count>
</div>
)
}
}
五. 复合组件写法
组件复合类似于在vue框架里面用的组件插槽
具体使用方式如下:
import React from 'react'
function FuncOne(props) {
return (
<div style="{{ border: `4px solid ${props.color || "blue"}` }}">
//等同于vue中匿名插槽
{props.children}
//等同于vue中具名插槽
<div className="abc">{props.footer}</div>
</div>
)
}
export default function FuncTwo() {
const confirmBtn = {
<button onClick={() => alert("学习React")}></button>
};
return (
<FuncOne color="green" footer={confirmBtn}>
<h3>Hello World!</h3>
</FuncOne>
)
}
第五节:高阶组件(HOC)
一. 高阶组件
1.高阶组件-- HOC(Higher-Order Components)
2.高阶组件是为了提高组件的服用率而出现的,抽离具有相同逻辑或逻辑或相同展示的组件;
3.高阶组件其实是一个函数,接收一个组件,然后返回一个新的组件,返回的这个新的组件可以对属性进行封装,也可以重写部分生命周期;
使用例子如下:
//创建withLearnReact高阶组件,传递一个组件进去,返回一个新的组件NewComponent
const withLearnReact = (Component) => {
const NewCompoent = (props) => {
return <Component {...props} name="张三丰" />
}
return NewCompoent
}
完整使用实例:
import React, { Component } from 'react'
//定义一个高阶组件,高阶组件一般以with开头,返回函数组件
const withLearnReact = (Component) => {
const NewCompoent = (props) => {
//一定要记得加上接收父组件的解构属性props
return <Component {...props} name="张翠山"></Component>
}
return NewCompoent
}
class HOC extends Component {
render() {
return (
<div>
<h3>高阶组件(HOC)</h3>
<p>姓名:{this.props.name}</p>
</div>
)
}
}
export default withLearnReact(HOC)
二.高阶组件的链式调用
使用情况如下:
编写一个高阶组件进行属性的添加;
编写一个高阶组件编写生命周期;
然后将以上两个高阶组件进行链式调用;
使用例子如下:
import React, { Component } from 'react'
//定义一个高阶组件,高阶组件一般以with开头,返回函数组件
const withLearnReact = (Component) => {
const NewCompoent = (props) => {
//一定要记得加上接收父组件的解构属性props
return <Component {...props} name="张翠山"></Component>
}
return NewCompoent
}
//定义第二个高阶组件,重写生命周期,注意重写生命周期需要class组件即返回的是class组件
const withLifeCycle = (Comp) => {
class NewCompoent extends Component {
//重写组件的生命周期
componentDidMount(){
console.log("重写componentDidMount生命周期")
}
render() {
return <Comp {...this.props}></Comp>
}
}
return NewCompoent
}
class HOC extends Component {
render() {
return (
<div>
<h3>高阶组件链式调用</h3>
<p>姓名:{this.props.name}</p>
</div>
)
}
}
export default withLifeCycle(withLearnReact(HOC))
三.高阶组件装饰器写法
由于高阶组件链式调用的写法看起来比较的麻烦也不好理解。逻辑会看的比较绕
ES7中就出现了装饰器的语法,专门拿来处理这种问题(类似于Java的注解)
安装支持装饰器语法的babel编译插件:
npm install -D @babel/plugin-proposal-decorators
更改配置文件config-overrides.js代码(修改后重启项目):
const { override, fixBabelImports, addDecoratorsLegacy } = require('customize-cra')
module.exports = override(
//配置antd按需加载
fixBabelImports('import', {
libraryName: 'antd',
libraryDirectory: 'es',
style: 'css',
}),
//配置支持高阶组件装饰器写法
/*addBabelPlugins(`
[
'@babel/plugin-proposal-derorators',
{legacy: true}
],
),*/
addDecoratorsLegacy()
);
组件完整代码如下:
import React, { Component } from 'react'
//定义一个高阶组件,高阶组件一般以with开头,返回函数组件
const withLearnReact = (Component) => {
const NewCompoent = (props) => {
//一定要记得加上接收父组件的解构属性props
return <Component {...props} name="张翠山"></Component>
}
return NewCompoent
}
//定义第二个高阶组件,重写生命周期,注意重写生命周期需要class组件即返回的是class组件
const withLifeCycle = (Comp) => {
class NewCompoent extends Component {
//重写组件的生命周期
componentDidMount(){
console.log("重写componentDidMount生命周期")
}
render() {
return <Comp {...this.props}></Comp>
}
}
return NewCompoent
}
@withLearnReact
@withLifeCycle
class HOC extends Component {
render() {
return (
<div>
<h3>高阶组件链式调用</h3>
<p>姓名:{this.props.name}</p>
</div>
)
}
}
export default HOC
四. 组件上下文(context)通信
1.上下文context有两个角色:
① Provider 数据提供
② Consumer 数据读取
2.使用context可以避免通过中间元素传递props,context的设计目的是为了共享对于一个组件树而言是"全局"的数据
不使用context的情况代码如下:
import React, { Component } from 'react'
let store = {
name = "张无忌",
age = 25,
}
class Info extends Component {
render() {
return (
<div>
<p>姓名:{this.props.name}</p>
<p>年龄:{this.props.age}</p>
</div>
)
}
}
function ToolBar(props) {
return (
<div>
<Info {...props}></Info>
</div>
)
}
export default class Context1 extends Component {
render() {
return (
<ToolBar name={store.name} from={store.age}></ToolBar>
)
}
}
使用context,避免中间props元素的传递写法,代码如下:
import React, { Component } from 'react'
//1.创建上下文
const XdContext = React.createContext()
const {Provider, Consumer} = XdContext
//创建一个传递的数据源
let store = {
name: '张无忌',
age: 25,
}
class Info extends Component {
render() {
return (
<Consumer>
{
store => {
return (
<div>
<p>姓名:{store.name}</p>
<p>年龄:{store.age}</p>
</div>
)
}
}
</Consumer>
)
}
}
function ToolBar(props) {
return (
<div>
<Info></Info>
</div>
)
}
export default class Context1 extends Component {
render(){
return (
<Provider value={store}>
<ToolBar></ToolBar>
</Provider>
)
}
}
第六节:React函数式编程之HOOK
一. React Hooks
1.Hook是React 16.8的新增特性。它可以让你在不编写class的情况下使用state以及其他的React特性。
2.在进行之前,记住以下:
①完全可选的。你无需重写任何已有代码就可以在一些组件中尝试Hook。但是如果你不想,你不必现在就去学习或使用Hook。
②100%向后兼容的。Hook不包含任何破坏性改动。
③现在可用。Hook已发布于v16.8.0
3.没有计划从React中移除class。
4.Hook不会影响你对React概念的理解。恰恰相反,Hook为已知的React概念提供了更直接的API: props,state, context, refs以及生命周期。接下来的学习我们会发现,Hook还提供了一种更强大的方式来组合他们。
5.React Hooks解决了什么问题:
①函数组件不能使用state,一般只用于一些简单无交互的组件,用作信息展示,即我们上面说的傻瓜组件使用,如果需要交互更改状态等复杂逻辑时就需要使用class组件了。React Hooks让我们更好的拥抱函数式编程,让函数式组件也能使用state功能,因为函数式组件比class组件更简洁好用,因为React Hooks的出现,相信未来我们会更多的使用函数式组件。
②副作用问题:
我们一般称数据获取、订阅、定时执行任务、手动更改ReactDOM这些行为都可以成为副作用;
由于React Hooks的出现,我们可以使用useEffect来处理组件副作用问题,所以我们的函数式组件也能进行副作用逻辑的处理了;
③有状态的逻辑重用组件
④复杂的状态管理:
之前我们使用redux、dva、mobx第三方状态管理器来进行复杂的状态管理;
现在我们可以使用useReducer、useContext配合使用实现复杂状态管理,不用再依赖第三方状态管理器
⑤开发效率和质量问题:
函数式组件比class组件简洁,开发的体验更好,效率更高同时应用的性能也更好;
封装好的React Hooks,可以来这里学习封装自定义Hooks
整理了N个实用案例帮你快速迁移到React Hooks(收藏慢慢看系列) 学习博客(强烈推荐)
二. useState特性
1.useState -- 组件状态管理钩子,能够使函数组件使用state
2.基本使用如下:
//state是要设置的状态
//setState是更新state的方法,只是一个方法名,可以随意更改
//initState是初始的state,可以随意的数据类型,也可以是回调函数,但是函数必须是有返回值
const [state, setState] = useState(initState)
完整使用例子代码如下:
import React, {useState} from 'react'
export default function HookUseState(){
const [count, setCount] = useState(0)
return (
<div>
<div>你点击了{count}次</div>
<button onClick={() => setCount(count+1)}>点击</button>
</div>
)
}
三. useEffect特性
1.useEffect -- 副作用处理钩子
①数据获取、订阅、定时执行任务、手动修改ReactDOM这些行为都可以称为副作用。而useEffect就是为了处理这些副作用而生的;
②useEffect也是componentDidMount、componentDidUpdate和componentWillUnmount这几个生命周期方法的统一;
2.useEffect的基本使用如下:
useEffect(callback, array)
①callback可以返回一个函数,用作清理:
useEffect(() => {
//副作用逻辑
xxxx
return () => {
//清理副作用需要清理的内容
//类似于componentWillUnmount,组件渲染和组件卸载前执行的代码
}
}, [])
② array(可选参数):数组,用于控制useEffect的执行,分三种情况:
空数组,则只会执行一次(即初次渲染render),相当于componentDidMount;
非空数组,useEffect会在数组发生后执行;
不填array这个数组,useEffect每次渲染都会执行;
完整使用例子代码如下:
import React, {useState, useEffect} from 'react'
export default function HookUseEffect() {
const [count, setCount] = useState(1)
useEffect(() => {
//更新页面标题
document.title = `您点击了${count}次了`
return () => {
console.log("组件卸载或更新了")
}
}, [count])
return(
<div>
<div>你点击了{count}次</div>
<button onClick={() => setCount(count+1)}>点击</button>
</div>
)
}
四.useContext特性
1.context就是用来更方便的实现全局数据共享的,但是由于它并不是那么好用,所以我们一般会使用第三方状态管理器来实现全局数据共享
① redux
② dva
③ mobx
2.useContext(context)是针对context上下文提出的一个Hooks提出的一个API,它接受React.createContext()的返回值作为参数,即context对象,并返回最近的context
3.使用useContext是不需要再使用Provider和Consumer的
4.当最近的context更新时,那么使用该context的hook将会重新渲染
基本使用如下:
import React, {useContext} from 'react'
const Context = React.createContext({age: '25', name: '张无忌'})
const ageComp = () => {
//使用useContext
const ctx = useContext(Context)
return (
<div>姓名:{ctx.name}</div>
<div>年龄:{ctx.age}岁</div>
)
}
使用实例一代码如下:
HookUseContext.js文件代码:
import React, {useContext} from 'react'
import HookUseContextChild from './HookUseContextChild'
const Context = React.createContext({name: '张无忌', age: 25})
export default function HookUseContext(){
const ctx = useContext(Context)
return (
<div>
<hr></hr>
<h3>使用UseContext</h3>
<p>姓名:{ctx.name}</p>
<p>年龄:{ctx.age}</p>
//需要给每一个引入的子组件传递Context
<HookUseContextChild Context={Context}></HookUseContextChild>
</div>
)
}
HookUseContextChild.js文件代码:
import React, {useContext} from 'react'
import {Context} from './HookUseContext'
export default function HookUseContextChild({Context}) {
const ctx = useContext(Context)
return (
<div>
<h3>子组件接收父组件传递的context</h3>
<p>姓名:{ctx.name}</p>
<p>年龄:{ctx.age}</p>
</div>
)
}
实例一需要给每一个引入的子组件传递Context,所以一般项目是将数据单独放到数据文件中,其他组件引入解构使用,实例二代码如下:
数据文件store.js代码:
import React from 'react'
export const tom = React.createContext({name: 'tom', age: 25})
export const jerry = React.createContext({name: 'jerry', age: 28})
HookUseContextOne.js文件代码:
import React, {useContext} from 'react'
import {tom} from './store'
export default function HookUseContextOne(){
const ctx = useContext(tom)
return (
<div>
<p>姓名:{ctx.name}</p>
<p>年量:{ctx.age}</p>
</div>
)
}
HookUseContextTwo.js文件代码:
import React, {useContext} from 'react'
import {jerry} from './store'
export default function HookUseContextTwo()
{
const ctx = useContext(jerry)
return (
<div>
<p>姓名:{ctx.name}</p>
<p>年龄:{ctx.age}</p>
</div>
)
}
五.useReducer特性
1.useReducer是useState的一个增强体,可以用于处理复杂的状态管理;
2.useReducer可以完全替代useState,只是我们简单的状态管理用useState比较易用,useReducer的设计灵感源自于redux的reducer
3.对比一下useState和useReducer的使用:
//useState的使用方法
const [state, setState] = useState(initState)
//useReducer的使用方法
const [state, dispatch] = useReducer(reducer, initState, initAction)
4.useReducer的参数介绍:
① reducer是一个函数,根据action状态处理并更新state
② initState初始化的state
③ initAction是useReducer初次执行时被处理的action
5.返回值state,dispath介绍:
① state状态值
② dispatch是更新state的方法,它接受action作为参数
6.useReducer只需要调用dispatch(action)方法传入action即可更新state,使用如下:
//dispatch是用来更新state的,当dispatch被调用的时候,reducer方法也会被调用,同时根据action的传入内容去更新state, action是传入一个描述操作的对象
dispatch({type: 'add'})
7.reducer是redux的产物,他是一个函数,主要用于处理action,然后返回最新的state,可以把reducer理解成是action和state的转换器,它会根据action的描述去更新state,使用例子:
(state, action) => Newstate
具体使用代码例子:
import React, {useReducer} from 'react'
const initState = {count: 0} //state的初始值
const reducer = (state, action) => {
switch(action.type) {
//当type是reset时,重置state的值回到初始化时候的值
case 'reset':
return initState
//当type的值是add时,让count+1
case 'add':
return {count: state.count+1}
//当type的值是reduce是,让count-1
case 'reduce':
return {count: state.count-1}
//当type不属于上面任意一个值,state不做更改,直接返回当前state
default:
return state
}
}
export default function HookUseReducer()
{
const [state, dispatch] = useReducer(reducer, initState)
return (
<div>
<p>当前数量是: {state.count}</p>
<p><button onClick={() => dispatch({type: 'reset'})}>重置</button></p>
<p><button onClick={() => dispatch({type: 'add'})}>加一</button></p>
<p><button onClick={() => dispatch({type: 'reduce'})}>减一</button></p>
</div>
)
}
六.useMemo特性
useMemo用于性能优化,通过记忆值来避免在每个渲染上执行高开销的计算
适用于发展的计算场景,例如复杂的列表渲染,对象深拷贝等场景;
使用方法如下:
const memoValue = useMemo(callback, array)
① callback是一个函数用于处理逻辑
② array控制useMemo重新执行的数组,array改变时才会重新执行useMemo
③ useMemo的返回值是一个记忆值,是callback的返回值
使用方法如下:
const obj1 = {name: '张无忌', age: 25}
const obj2 = {name: '张敏敏', age: 24}
const memoValue = useMemo(() => Object.assign(obj1, obj2), [obj1, obj2])
//使用方式
<div>姓名:{memoValue.name} --- 年龄:{memoValue.age}</div>
不能在useMemo里面写副作用逻辑处理,副作用的逻辑处理放在useEffect内进行处理。
七.useCallback特性
useCallback特性和useMemo一样,也是用于性能优化的
基本使用方法:
const memoCallback = useCallback(callback, array)
① callback是一个函数用于逻辑处理;
② array控制useCallback重新执行的数组,array改变时才会重新执行useCallback;
③ 根useMemo不一样的是返回值是callback本身,而useMemo返回的是callback函数的返回值;
使用方法如下:
const obj1 = {name: '张无忌', age: 25}
const obj2 = {name: '张敏敏', age: 24}
const memoCallback = useCallback(() => Object.assign(obj1, obj2), [obj1, obj2])
//使用方式
<div>姓名:{memoCallback().name} --- 年龄:{memoCallback().age}</div>
八.useRef特性
方便我们访问操作dom
使用方法如下:
import React, {useRef} from 'react'
const UseRefComp = () => {
//创建ref
const inputRef = useRef
const getValue = () => {
console.log(inputRef.current.value)
}
//挂载
return (
<div>
<input ref={inputRef} type='text' />
<button onClick={getValue}>获取input值</button>
</div>
)
}
九.自定义Hooks
Hooks其实就是一个封装好的钩子函数供我们调用
只是我们自己封装的时候要特别注重性能,重复渲染这些问题,官方封装的就比较完美;
简单封装一个改变页面标题的自定义Hooks:
import React, {useEffect} from 'react'
//封装的Hooks用use开头
const useChangeTitle = (title) => {
useEffect(() => {
document.title = title
}, [title])
}
export default (props) => {
useChangeTitle('自定义修改标题Hooks')
return(
<div>
测试自定义Hooks
</div>
)
}
十.总结React Hooks使用规则
1.只在顶层调用Hooks
① Hooks的调用尽量只在顶层作用域进行调用;
② 不要在循环,条件或者是嵌套函数中调用Hook,否则可能会无法确保每次组件渲染时都以相同的顺序调用Hook
2.只在函数组件调用Hooks
① React Hooks目前只支持函数组件,所以别在class组件或者普通的函数里面调用Hook钩子函数;
3.React Hooks的应用场景如下:
① 函数组件
② 自定义组件
4.在未来的版本React Hooks会扩展到class组件,但是现阶段不能在class里使用
第七节:状态管理器Redux
一.Redux成员及其数据流
1.actions
① actions其实是描述操作的对象,我们调用dispatch时需要传入次对象;
2.store
① store是整个应用的数据存储仓库,把我们全局管理的状态数据存储起来;
3.reducers
① reducers接收action并更新store
4.注意:redux是一个单独的数据流框架,根react并没有直接的联系,我们也可以在其他复杂项目里使用redux进行数据管理,当我们不知道是否应该使用redux的时候,我们都是不需要的,因为只有我们很肯定redux能帮助我们管理好复杂项目数据流的时候他才能发挥它的威力,简单的项目我们只需要state+props+context就够了
二.redux编写累加器
安装redux
npm install redux --save
编写使用redux的步骤:
1.从redux引入createStore用来创建数据仓库store
① createStore是一个函数,需要传入reducer作为参数,返回值是我们需要的store;
2.在使用页面引入数据仓库store
① 通过getState()办法可以获取到数据仓库里的状态数据state
② 通过dispatch(action)可以触发更改reducer函数
③ 每次触发dispatch都会触发store.subscribe()方法,用来从新触发页面渲染
代码展示:
项目index.js文件代码:
import './index.css';
import App from './App';
import store from './Redux/store';
const render=() => ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
render()
store.subscribe(render)
store.js文件代码:
import {createStore} from 'redux'
const firstReducer = (state=0, action) => {
switch(action.type) {
//当传入action的type为add的时候给state+1
case 'add':
return state+1
//当传入action的type为reduce的时候给state-1
case 'reduce':
return state-1
default:
return state
}
}
//创建数据仓库,把我们编写的reducer作为参数传入createStore
const store = createStore(firstReducer)
export default store
FirstRedux.js文件代码:
import React, {Component} from 'react'
import store from './store'
export default class FirstRedux extends Component
{
render(){
return (
<div>
<hr />
<h2>尝试使用redux编写一个累加器</h2>
//通过getState方法获取数据仓库里面的状态数据state
{store.getState()}
<div>
<button onClick={() => store.dispatch({type: 'add'})}>加一</button>
<button onClick={() => store.dispatch({type: 'reduce'})}>减一</button>
</div>
</div>
)
}
}
三.react-redux进行改造累加器
1.由于redux的写法太繁琐,还每次都需要重新调用render,不太复合我们了解react编程;
2.react-redux出境,安装react-redux:
npm install react-redux --save
3.React-redux提供两个api供我们使用:
① Provider 顶级组件,功能为给我们提供数据;
② connect 高阶组件,功能为提供数据和方法;
4.以下为使用react-redux改造累加器的代码,只需留意index.js和FirstRedux.js,store.js暂时不用做改变
代码展示:
FirstRedux.js文件代码:
import React, {Component} from 'react'
import {connect} from 'react-redux'
//写一个返回数据的方法,供connect使用,connect会帮我们把数据转成props
const mapStateToProps = (state) => {
return {
count: state
}
}
//写一个返回dispatch方法的方法供connect使用,connect帮我们把dispatch转成props
const mapDispatchToProps = dispatch => {
return {
add: () => dispatch({type: 'add'}),
reduce: () => dispatch({type: 'reduce'})
}
}
class FirstRedux extends Component {
render() {
return (
<div>
<h2>使用redux编写累加器</h2>
{this.props.count}
<div>
<button onClick={() => this.props.add()}>加一</button>
<button onClick={() => this.props.reduce()}>减一</button>
</div>
</div>
)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(FirstRedux)
项目index.js文件代码:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import store from './Redux/store';
import {Provider} from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
四.高阶组件使用装饰器模式
connect高阶组件用装饰器模式会使我们的代码看起来更简洁易懂;
使用装饰器进行我们的代码优化:
FirstRedux.js文件代码:
import React, {Component} from 'react'
import {connect} from 'react-redux'
@connect(
state => ({count:state}),
dispatch => ({
add:() => dispatch({type: 'add'}),
reduce:() => dispatch({type: 'reduce'})
})
)
class FirstRedux extends Component {
render() {
return (
<div>
<h2>使用装饰器优化累加器</h2>
{this.props.count}
<div>
<button onClick={() => this.props.add()}>加一</button>
<button onClick={() => this.props.reduce()}>减一</button>
</div>
</div>
)
}
}
export default FirstRedux
五.中间件redux-thunk于redux-logger
由于redux reducer默认只支持同步,实现异步任务或者延时任务时,我们就要借助中间件的支持了;
1.安装redux-thunk,支持我们reducer在异步操作结束后自动执行
npm install redux-thunk --save
2.安装redux-logger,打印日志记录协助本地测试
npm install redux-logger --save
使用代码如下:
store.js文件代码:
import {createStore, applyMiddleware} from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
const firstReducer = (state=0, action) => {
console.log(action)
switch(action.type) {
//当传入action的type为add的时候给state+1
case 'add':
return state + 1
//当传入actio的type为reduce的时候给state-1
case 'reduce':
return state - 1
default:
return state
}
}
//创建数据仓库,把我们编写的reducer作为参数传入createStore
//有一个注意点就是logger最好放在最后,日志最后输出才不会出bug,因为中间件时按顺序执行
const store = createStore(firstReducer, applyMiddleware(thunk, logger))
export default store
FirstRedux.js文件代码:
import React, {Component} from 'react'
import {connect} from 'react-redux'
@connect(
state => ({count:state}),
dispatch => ({
add:() => dispatch({type: 'add'}),
reduce:() => dispatch({type: 'reduce'}),
addSync:() => setTimeout(() => {
//延时2s后cout加一
dispatch({type: 'add'})
}, 2000),
})
)
class FirstRedux extends Component {
render() {
return (
<div>
<h2>使用装饰器优化累加器</h2>
{this.props.count}
<div>
<button onClick={() => this.props.add()}>加一</button>
<button onClick={() => this.props.reduce()}>减一</button>
<button onClick={() => this.props.addSync()}>延时加一</button>
</div>
</div>
)
}
}
export default FirstRedux
六.抽离reducer和action进行统一管理
第一步新建一个count.redux.js存放我们的reducer和action
count.redux.js文件代码:
//把reducer和action抽离出来再同一个文件下进行维护
const firstReducer = (state=0, action) => {
console.log(action)
switch(action.type) {
case 'add':
return state + 1
case 'reduce':
return state - 1
default:
return state
}
}
const add = () => ({type: 'add'})
const reduce = () => ({type: 'reduce'})
const addSync = () => dispatch => {
setTimeout(() => {
dispatch({type: 'add'})
}, 2000)
}
export {firstReducer, add, reduce, addSync}
FirstRedux.js文件代码:
import React, {Component} from 'react'
import {connect} from 'react-redux'
import {add, reduce, addSync} from './count.redux'
@connect(
state => ({count:state}),
{add, reduce, addSync}
/* dispatch => ({
add:() => dispatch({type: 'add'}),
reduce:() => dispatch({type: 'reduce'}),
addSync:() => setTimeout(() => {
//延时2s后cout加一
dispatch({type: 'add'})
}, 2000),
}) */
)
class FirstRedux extends Component {
render() {
return (
<div>
<h2>抽离reducer和action进行统一管理</h2>
{this.props.count}
<div>
<button onClick={() => this.props.add()}>加一</button>
<button onClick={() => this.props.reduce()}>减一</button>
<button onClick={() => this.props.addSync()}>延时加一</button>
</div>
</div>
)
}
}
export default FirstRedux
项目index.js文件代码:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import {Provider} from 'react-redux';
import {createStore, applyMiddleware} from 'redux'
import logger from 'redux-logger'
import thunk from 'redux-thunk'
import {firstReducer} from './Redux/count.redux';
const store = createStore(firstReducer, applyMiddleware(thunk, logger))
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);