文章目录

  • React 入门
    • React 简介
      • React 为何物
      • 为何学习 React
    • React 初体验
      • 来一发 Hello React
      • 创建虚拟 DOM 的两种方式:JS 和 JSX
      • 虚拟 DOM && 真实 DOM
    • **JSX**
      • JSX 简介
      • ==**JSX 语法规则**==
      • JSX 例子
  • React 面向组件编程
    • 函数式组件
    • 类式组件
    • 三大组件实例核心属性 --state
    • 组件实例核心属性 props
      • props 基本使用
      • 批量传递 props
      • 限制 传递的props
      • props 的简写形式
      • 类式组件的构造器与 props
      • 函数式组件使用 props
    • 组件实例核心属性 refs
      • 字符串形式的 ref
      • 回调形式的 ref
      • createRef API
      • 事件处理
    • 受控 & 非受控组件
    • 生命周期
      • 生命周期旧版
      • 生命周期新版
      • 最重要的三个钩子
    • 虚拟 DOM 与 Diff 算法
  • React 脚手架
    • 创建 React 项目
    • React 脚手架项目结构
    • 样式的模块化
    • 组件化开发思想
      • TodoList 案例总结
  • React 网络请求
    • React 脚手架配置代理
    • **消息订阅-发布机制**
    • Github 搜索框案例知识点总结
  • React 路由
    • 路由的理解
    • 路由基本使用
    • 路由组件和一般组件
    • NavLink 的使用
    • Switch 的使用
    • 解决多级路径刷新页面样式丢失的问题
    • 路由的严格匹配与模糊匹配
    • Redirect 的使用
    • 嵌套路由
    • 动态路由传参
      • 第一种 params 传参
      • 第二种 search 传参
      • 第三种 state 传参
    • 编程式导航
    • withRouter 的使用
    • BrowserRouter 和 HashRouter
  • React UI 组件库
    • Ant Design 配置按需引入和自定义主题
    • 详细用法前去官方文档
  • Redux
    • Redux 概述
    • 完整版 redux 的使用
      • 1、 创建 redux 文件夹下的 store.js
      • 2、 创建 redux 文件夹下的 count_reducer.js (用于指定操作状态值)
      • 3、 创建 redux 文件夹下的 count_action.js (用于创建action对象 / 异步函数)
      • 4、 在需要使用的地方引入
    • 核心概念(api)
      • combineReducers()
    • 注意事项
    • Redux 异步编程
  • React-Redux
      • 相关API
        • ① Provider
        • ② `connect()()`
          • Ⅰ-mapStateToProps
          • Ⅱ-mapDispatchToProps
          • Ⅲ-代码示例
    • 多个组件数据共享
      • 添加案列
    • 项目打包运行
  • React 扩展内容
    • setState 更新状态的两种写法
    • 路由组件懒加载 lazyLoad
    • *React Hook*
      • State Hook
      • Effect Hook
      • Ref Hook
    • Fragment
    • Context
    • 组件渲染优化
    • render props (插槽)
    • 错误边界
    • 组件通信方式总结
  • 自定义 Hook
    • 注意事项
      • 解决不能在类式组件里使用hook的方法
      • Using Hooks as HOC
    • 获取窗口视口大小
    • 点击按钮更改名字,并展示延迟等待
    • 获取鼠标视口

React 入门

React 简介

React 为何物

React:用于构建用户界面的 JavaScript 库。由 Facebook 开发且开源。

为何学习 React

原生 JavaScript 的痛点:

  • 操作 DOM 繁琐、效率低
  • 使用 JavaScript 直接操作 DOM,浏览器进行大量重绘重排
  • 原生 JavaScript 没有组件化编码方案,代码复用率低

React 的特点:

  • 采用组件化模式、声明式编码,提高开发效率和组件复用率
  • React Native 中可用 React 语法进行移动端开发
  • 使用虚拟 DOM 和 Diffing 算法,减少与真实 DOM 的交互

React 初体验

来一发 Hello React

相关 JS 库:

  • react.development.js :React 核心库
  • react-dom.development.js :提供 DOM 操作的 React 扩展库
  • babel.min.js :解析 JSX 语法,转换为 JS 代码
<!-- 准备好一个“容器” -->
<div id="test"></div><!-- 引入react核心库 -->
<script type="text/javascript" src="../js/react.development.js"></script>
<!-- 引入react-dom,用于支持react操作DOM -->
<script type="text/javascript" src="../js/react-dom.development.js"></script>
<!-- 引入babel,用于将jsx转为js -->
<script type="text/javascript" src="../js/babel.min.js"></script><!-- 此处一定要写babel,表示写的不是 JS,而是 JSX,并且靠 babel 翻译 -->
<script type="text/babel">//1.创建虚拟DOM// 不要写引号,因为不是字符串const VDOM = <h1>Hello,React</h1>//2.渲染虚拟DOM到页面// 导入核心库和扩展库后,会有 React 和 ReactDOM 两个对象ReactDOM.render(VDOM, document.getElementById('test'))
</script>

创建虚拟 DOM 的两种方式:JS 和 JSX

  • 使用 JS 创建虚拟 DOM 比 JSX 繁琐
  • JSX 可以让程序员更加简单地创建虚拟 DOM,相当于语法糖
  • 最终 babel 会把 JSX 语法转换为 JS

JS创建

<script type="text/javascript">//1.使用 React 提供的 API 创建虚拟DOMconst VDOM = React.createElement('h1', { id: 'title' }, React.createElement('span', {}, 'Hello,React'))//2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'))
</script>

JSX创建(最终babel转为js)

<script type="text/babel">//1.创建虚拟DOMconst VDOM = (<h1 id="title"><span>Hello,React</span></h1>)//2.渲染虚拟DOM到页面ReactDOM.render(VDOM, document.getElementById('test'))
</script>

虚拟 DOM && 真实 DOM

关于虚拟 DOM:

  1. 本质是 Object 类型的对象(一般对象)
  2. 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
  3. 虚拟 DOM 最终会被 React 转化为真实 DOM,呈现在页面上。
<script >const VDOM = (<h1 id="title"><span>Hello,React</span></h1>)ReactDOM.render(VDOM, document.getElementById('test'))const TDOM = document.getElementById('demo')console.log('虚拟DOM', VDOM)console.log('真实DOM', [TDOM])
</script>

JSX

JSX 简介

  • 全称:JavaScript XML

  • React 定义的类似于 XML 的 JS 扩展语法;本质是 React.createElement() 方法的语法糖

    XML :

    <note>
    <to>George</to>
    <from>John</from>
    <heading>Reminder</heading>
    <body>Don't forget the meeting!</body>
    </note>
    
  • 作用:简化创建虚拟 DOM

JSX 语法规则

  • 定义虚拟 DOM 时,不要写引号
  • 标签中混入 JS 表达式需要使用 {}
  • 指定类名不用 class,使用 className
  • 内联样式,使用 style={ { key: value } } 的形式
  • 只能有一个根标签
  • 标签必须闭合,单标签结尾必须添加 /<input type="text" />
  • 标签首字母小写,则把标签转换为 HTML 对应的标签,若没有,则报错
  • 标签首字母大写,则渲染对应组件,若没有定义组件,则报错
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><title>jsx语法规则</title><style>.title {background-color: orange;width: 200px;}</style></head><body><div id="test"></div>...<script >const myId = 'aTgUiGu'const myData = 'HeLlo,rEaCt'const VDOM = (<div><h2 className="title" id={myId.toLowerCase()}><span style={{ color: 'white', fontSize: '19px' }}>{myData.toLowerCase()}</span></h2><input type="text" />// <good>very good</good>// <Child></Child></div>)ReactDOM.render(VDOM, document.getElementById('test'))</script></body>
</html>

JSX 例子

注意区分:JS 语句(代码)JS 表达式

  1. 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
a
a + b
demo(1)
arr.map()
function test() {}

​ 2. 语句(代码):

if(){}
for(){}
switch(){case:xxxx}

3.循环遍历数组

<script >let list = ['Angular', 'React', 'Vue'] const VDOM = (<div><h1>前端js框架列表</h1><ul>// React 会自动遍历数组{list.map((item, index) => {// Each child in a list should have a unique "key" prop.return <li key={index}>{item}</li>})}</ul></div>) ReactDOM.render(VDOM, document.getElementById('test'))
</script>

React 面向组件编程

函数式组件

<script >//1.创建函数式组件function MyComponent() {//此处的 this 是 undefined,因为 babel 编译后开启了严格模式console.log(this)return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>}//2.渲染组件到页面ReactDOM.render(<MyComponent />, document.getElementById('test'))/*渲染组件的过程:
- React 解析标签,寻找对应组件
- 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中
*/
</script>

要点:

  • 组件名称首字母必须大写,否则会解析成普通标签导致报错,详见 JSX 语法规则
  • 函数需返回一个虚拟 DOM
  • 渲染组件时需要使用标签形式,同时标签必须闭合

渲染组件的过程:

  • React 解析标签,寻找对应组件
  • 发现组件是函数式组件,则调用函数,将返回的虚拟 DOM 转换为真实 DOM ,并渲染到页面中

类式组件

<script>// 创建类式组件// 不同点 要class 类并且继承React.Componentclass MyComponent extends React.Component {render() {console.log('render中的this:', this) // this 指向实例对象return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>}}ReactDOM.render(<MyComponent />, document.getElementById('test'))
</script>

组件渲染过程:

  • React 解析组件标签,寻找组件
  • 发现是类式组件,则 new 该类的实例对象,通过实例调用原型上的 render 方法
  • render 返回的虚拟 DOM 转为真实 DOM ,渲染到页面上

三大组件实例核心属性 --state

state 是组件实例对象最重要的属性,值为对象。又称为状态机,通过更新组件的 state 来更新对应的页面显示。

要点:

  • 初始化 state
  • React 中事件绑定
  • this 指向问题
  • setState 修改 state 状态
  • constructorrender 、自定义方法的调用次数
<script >class Weather extends React.Component {// 调用一次constructor(props) {super(props)// 初始化 statethis.state = { isHot: true, wind: '微风' }// 解决 this 指向问题  不加这个的话,函数是开启严格模式下的直接调用this会输出undefinedthis.changeWeather = this.changeWeather.bind(this)}// 调用 1+N 次render() {// 读取状态const { isHot } = this.state// 事件绑定return <h1 onClick={this.changeWeather}>今天天气 {isHot ? '炎热' : '凉爽'}</h1>/* onClick={this.changeWeather}  不加this的话调用不到函数等于play(){这样直接调用调用不到原型上的study一样,得加this才能调用到study()}*/}// 点一次调一次changeWeather() {const isHot = this.state.isHot// 对 state 的修改是一种合并而非替换,即 wind 依然存在this.setState({ isHot: !isHot })}}ReactDOM.render(<Weather />, document.getElementById('test'))
</script>

简化版:

<script>class Weather extends React.Component {state = { isHot: true, wind: '微风' }render() {const { isHot } = this.statereturn <h2 onClick={this.changeWeather}>天气{isHot ? '炎热' : '凉爽'}</h2>}// 采用箭头函数 + 赋值语句形式changeWeather = () => {const isHot = this.state.isHotthis.setState = { isHot: !isHot }}}ReactDOM.render(<Weather />, document.getElementById('test'))
</script>

组件实例核心属性 props

每个组件对象都有 props 属性,组件标签的属性都保存在 props 中。

props 是只读的,不能修改。

props 基本使用

<script>class Person extends React.Component {render() {const { name, age, sex } = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}// 类似于标签属性传值ReactDOM.render(<Person name="Lily" age={19} sex="男" />, document.getElementById('test'))
</script>

批量传递 props

<script >class Person extends React.Component {render() {const { name, age, sex } = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}const obj = { name: 'Ben', age: 21, sex: '女' }ReactDOM.render(<Person {...obj} />, document.getElementById('test'))
</script>

限制 传递的props

React 15.5 以前,React 身上有一个 PropTypes 属性可直接使用,即 name: React.PropTypes.string.isRequired ,没有把 PropTypes 单独封装为一个模块。

React 15.5 开始,把 PropTypes 单独封装为一个模块,需要额外导入使用。

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script><script >class Person extends React.Component {render() {const { name, age, sex } = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}// 类型和必要性限制// 注意Person.propTypes 和 PropTypes  Person.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,// 限制 speak 为函数speak: PropTypes.func,}// 指定默认值Person.defaultProps = {sex: 'male',age: 19,}ReactDOM.render(<Person name="Vue" sex="male" age={11} speak={speak} />, document.getElementById('test'))function speak() {console.log('speaking...')}
</script>

props 的简写形式

Person.propTypesPerson.defaultProps 可以看作在类身上添加属性,利用 static 关键词就能在类内部进行声明。因此所谓简写只是从类外部移到类内部。

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script><script >class Person extends React.Component {static propTypes = {// 必修为字符串且必填name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,// 限制 speak 为函数speak: PropTypes.func,}// static 是添加到Person上的属性static defaultProps = {sex: 'male',age: 19,}render() {const { name, age, sex } = this.propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}}ReactDOM.render(<Person name="Vue" sex="male" age={11} speak={speak} />, document.getElementById('test'))function speak() {console.log('speaking...')}
</script>

类式组件的构造器与 props

官网文档说明(opens new window)

构造函数一般用在两种情况:

  • 通过给 this.state 赋值对象来初始化内部 state
  • 为事件处理函数绑定实例
constructor(props) {super(props)// 初始化 statethis.state = { isHot: true, wind: '微风' }// 解决 this 指向问题this.changeWeather = this.changeWeather.bind(this)
}

因此构造器一般都不需要写。如果要在构造器内使用 this.props 才声明构造器,并且需要在最开始调用 super(props)

否则this.props 就会变为undefined ,不过一般也可以调用props

constructor(props) {super(props)console.log(this.props)
}

函数式组件使用 props

由于函数可以传递参数,因此函数式组件可以使用 props

<!-- 引入prop-types,用于对组件标签属性进行限制 -->
<script type="text/javascript" src="../js/prop-types.js"></script><script>function Person(props) {const { name, age, sex } = propsreturn (<ul><li>姓名:{name}</li><li>性别:{sex}</li><li>年龄:{age}</li></ul>)}// 限制就必须写在函数外面了,因为函数里面this丢失(开启了严格模式)Person.propTypes = {name: PropTypes.string.isRequired,sex: PropTypes.string,age: PropTypes.number,}Person.defaultProps = {sex: '男',age: 18,}ReactDOM.render(<Person name="jerry" />, document.getElementById('test'))
</script>

组件实例核心属性 refs

通过定义 ref 属性可以给标签添加标识。即获取该节点

字符串形式的 ref

这种形式已过时,效率不高,官方 (opens new window)不建议使用。

<script>class Demo extends React.Component {showData = () => {const { input1 } = this.refsalert(input1.value)}render() {return (<div><input ref="input1" type="text" placeholder="点击按钮提示数据" />&nbsp;<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;</div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>

回调形式的 ref

要点:

  • c => this.input1 = c 就是给组件实例添加 input1 属性,值为节点(currentNode)
  • 由于是箭头函数,因此 thisrender 函数里的 this ,即组件实例
<script>class Demo extends React.Component {showData = () => {// 直接从this 里取出input1 属性const { input1 } = thisalert(input1.value)}render() {return (<div><input ref={(c) => {this.input1 = c }}type="text" placeholder="点击按钮提示数据" />&nbsp;<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;</div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>

关于回调 ref 执行次数的问题,官网 (opens new window)描述:

TIP

如果 ref 回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数 null,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的 ref 并且设置新的。通过将 ref 的回调函数定义成 class 的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。

即内联函数形式,在更新过程重新调用renderref 回调会被执行两次,第一次传入 null ,第二次传入 DOM 元素。若是下述形式,则只执行一次。但是对功能实现没有影响,因此一般也是用内联函数形式。

函数定义成 class 的绑定函数的方式

<script>//创建组件class Demo extends React.Component {state = { isHot: false }changeWeather = () => {const { isHot } = this.statethis.setState({ isHot: !isHot })}saveInput = (c) => {this.input1 = cconsole.log('@', c)}render() {const { isHot } = this.statereturn (<div><h2>今天天气很{isHot ? '炎热' : '凉爽'}</h2><input ref={this.saveInput} type="text" /></div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>

createRef API

该方式通过调用 React.createRef 返回一个容器用于存储节点,且一个容器只能存储一个节点。(官方推荐的用法)

<script>class Demo extends React.Component {myRef = React.createRef()myRef2 = React.createRef()showData = () => {alert(this.myRef.current.value)}showData2 = () => {alert(this.myRef2.current.value)}render() {return (<div><input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />&nbsp;<button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;<input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" />&nbsp;</div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>

事件处理

  • React 使用自定义事件,而非原生 DOM 事件,即 onClick、onBlur :为了更好的兼容性
  • React 的事件通过事件委托方式进行处理:为了高效
  • 通过 event.target 可获取触发事件的 DOM 元素:勿过度使用 ref

当触发事件的元素和需要操作的元素为同一个时,可以不使用 ref

class Demo extends React.Component {showData2 = (event) => {alert(event.target.value)}render() {return (<div><input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />&nbsp;</div>)}
}

受控 & 非受控组件

包含表单的组件分类:

  • 非受控组件:现用现取。即需要使用时,再获取节点得到数据
  • 受控组件:类似于 Vue 双向绑定的从视图层绑定到数据层

尽量使用受控组件,因为非受控组件需要使用大量的 ref

// 非受控组件
class Login extends React.Component {handleSubmit = (event) => {// 阻止表单提交的默认事件event.preventDefault()const { username, password } = thisalert(`用户名是:${username.value}, 密码是:${password.value}`)}render() {return (<form onSubmit={this.handleSubmit}>用户名:<input ref={(c) => (this.username = c)} type="text" name="username" />密码:<input ref={(c) => (this.password = c)} type="password" name="password" /><button>登录</button></form>)}
}
// 受控组件
class Login extends React.Component {state = {username: '',password: '',}saveUsername = (event) => {this.setState({ username: event.target.value })}savePassword = (event) => {this.setState({ password: event.target.value })}handleSubmit = (event) => {event.preventDefault()const { username, password } = this.statealert(`用户名是:${username}, 密码是:${password}`)}render() {return (<form onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveUsername} type="text" name="username" />密码:// onChange input改变一次触发一次<input onChange={this.savePassword} type="password" name="password" /><button>登录</button></form>)}
}

对上述受控组件的代码进行优化,希望把 saveUsernamesavePassword 合并为一个函数。

要点:

  • 高阶函数:参数为函数或者返回一个函数的函数,如 Promise、setTimeout、Array.map()
  • 函数柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式
// 函数柯里化
function sum(a) {return (b) => {return (c) => {return a + b + c}}
}
// 使用高阶函数和柯里化写法
class Login extends React.Component {state = {username: '',password: '',}saveFormData = (dataType) => {return (event) => {// 对象里传入属性this.setState({ [dataType]: event.target.value })}}handleSubmit = (event) => {event.preventDefault()const { username, password } = this.statealert(`用户名是:${username}, 密码是:${password}`)}render() {return (<form onSubmit={this.handleSubmit}>用户名:<input onChange={this.saveFormData('username')} type="text" name="username" />密码:<input onChange={this.saveFormData('password')} type="password" name="password" /><button>登录</button></form>)}
}
/* 不使用柯里化写法
onChange={(event) => this.saveFormData('username', event)} 主要改变在这既可以接受传入的Str和事件获取里面的值
*/
class Login extends React.Component {state = {username: '',password: '',}// 简化不用高阶函数saveFormData = (dataType, event) => {this.setState({ [dataType]: event.target.value })}handleSubmit = (event) => {event.preventDefault()const { username, password } = this.statealert(`用户名是:${username}, 密码是:${password}`)}render() {return (<form onSubmit={this.handleSubmit}>用户名:<input onChange={(event) => this.saveFormData('username', event)} type="text" name="username" />密码:<input onChange={(event) => this.saveFormData('password', event)} type="password" name="password" /><button>登录</button></form>)}
}

生命周期

生命周期旧版

初始化阶段ReactDOM.render() 触发的初次渲染

  • constructor
  • componentWillMount (组件将要挂载时调用)
  • render
  • componentDidMount (组件挂载完毕调用)

更新阶段

  1. 父组件重新 render 触发的更新
  • componentWillReceiveProps ( 父组件更新数据传值时调用,并不是一开始就会调用可以在里面接受到props值)

  • shouldComponentUpdate :控制组件是否更新的阀门,返回值为布尔值,默认为 true 。若返回 false ,则后续流程不会进行。

  • componentWillUpdate

  • render

  • componentDidUpdate (组件完成数据更新)

    2.组件内部调用 this.setState() 修改状态

  • shouldComponentUpdate ( 阀门,必修return 一个布尔值,true 就往下执行,false 就不执行即不更新页面 )

  • componentWillUpdate

  • render

  • componentDidUpdate

​ 3.组件内部调用 this.forceUpdate() 强制更新 (数据不更新页面也重新渲染时调用)

  • componentWillUpdate
  • render
  • componentDidUpdate

卸载阶段ReactDOM.unmountComponentAtNode() 触发

  • componentWillUnmount

生命周期新版

更改内容 (opens new window):

  • 废弃三个钩子:componentWillMountcomponentWillReceivePropscomponentWillUpdate 。在新版本中这三个钩子需要加 UNSAFE_ 前缀才能使用,后续可能会废弃。
  • 新增两个钩子(实际场景用得很少):getDerivedStateFromPropsgetSnapshotBeforeUpdate

static getDerivedStateFromProps(props, state) (opens new window):

  • 需使用 static 修饰
  • 需返回一个对象更新 state 或返回 null
  • 适用于如下情况:state 的值任何时候都取决于 props

getSnapshotBeforeUpdate(prevProps, prevState) (opens new window):

  • 在组件更新之前获取快照
  • 得组件能在发生更改之前从 DOM 中捕获一些信息(如滚动位置)
  • 返回值将作为参数传递给 componentDidUpdate()
class {static getDerivedStateFromProps(props,state){console.log('getDerivedStateFromProps',props,state);return null
}getSnapshotBeforeUpdate(){console.log('getSnapshotBeforeUpdate');return 'atguigu'
}componentDidUpdate(preProps,preState,snapshotValue){console.log('componentDidUpdate',preProps,preState,snapshotValue);
}
}
// getSnapshotBeforeUpdate 案例
class NewsList extends React.Component {state = { newsArr: [] }componentDidMount() {setInterval(() => {//获取原状态const { newsArr } = this.state//模拟一条新闻const news = '新闻' + (newsArr.length + 1)//更新状态this.setState({ newsArr: [news, ...newsArr] })}, 1000)}getSnapshotBeforeUpdate() {return this.refs.list.scrollHeight}componentDidUpdate(preProps, preState, height) {// 每次都 往上滚 对应添加的节点距离this.refs.list.scrollTop += this.refs.list.scrollHeight - height}render() {return (<div className="list" ref="list">{this.state.newsArr.map((n, index) => {return (<div key={index} className="news">{n}</div>)})}</div>)}
}
ReactDOM.render(<NewsList />, document.getElementById('test'))

最重要的三个钩子

  • render :初始化渲染和更新渲染
  • componentDidMount :进行初始化,如开启定时器、发送网络请求、订阅消息
  • componentWillUnmount :进行收尾,如关闭定时器、取消订阅消息

虚拟 DOM 与 Diff 算法

key 的作用:

key 是虚拟 DOM 对象的标识,可提高页面更新渲染的效率。

当状态中的数据发生变化时,React 会根据新数据生成新的虚拟 DOM ,接着对新旧虚拟 DOM 进行 Diff 比较,规则如下:

  • 旧虚拟 DOM 找到和新虚拟 DOM 相同的 key:

    • 若内容没变,直接复用真实 DOM
    • 若内容改变,则生成新的真实 DOM ,替换页面中之前的真实 DOM
  • 旧虚拟 DOM 未找到和新虚拟 DOM 相同的 key:根据数据创建新的真实 DOM ,渲染到页面

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

  • 若对数据进行逆序添加、逆序删除等破坏顺序的操作,会进行没有必要的真实 DOM 更新。界面效果没问题,但效率低下。
  • 如果结构中包含输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。
  • 若不存在对数据逆序添加、逆序删除等破坏顺序的操作,则没有问题。
// 使用 index 作为 key 引发的问题
class Person extends React.Component {state = {persons: [{ id: 1, name: '小张', age: 18 },{ id: 2, name: '小李', age: 19 },],}add = () => {const { persons } = this.stateconst p = { id: persons.length + 1, name: '小王', age: 20 }this.setState({ persons: [p, ...persons] })}render() {return (<div><h2>展示人员信息</h2><button onClick={this.add}>添加小王</button><h3>使用index作为key</h3><ul>{this.state.persons.map((personObj, index) => {return (// 输入类的 DOM(如 input 输入框) ,则会产生错误的 DOM 更新。并且渲染效率低<li key={index}>{personObj.name}---{personObj.age}<input type="text" /></li>)})}</div>)}
}

React 脚手架

创建 React 项目

  • 全局安装 React 脚手架:npm i -g create-react-app
  • 创建项目:create-react-app 项目名称
  • 进入文件夹:cd 项目名称
  • 启动项目:npm start

React 脚手架项目结构

public :静态资源文件

  • manifest.json :应用加壳(把网页变成安卓/IOS 软件)的配置文件
  • robots.txt :爬虫协议文件

src :源码文件

  • App.test.js :用于给 App 组件做测试,一般不用
  • index.js :入口文件
  • reportWebVitals.js :页面性能分析文件,需要 web-vitals 库支持
  • setupTests.js :组件单元测试文件,需要 jest-dom 库支持

index.html 代码分析:

<!DOCTYPE html>
<html lang="en"><head><meta charset="utf-8" /><!-- %PUBLIC_URL% 代表 public 文件夹的路径 --><link rel="icon" href="%PUBLIC_URL%/favicon.ico" /><!-- 开启理想视口,用于做移动端网页的适配 --><meta name="viewport" content="width=device-width, initial-scale=1" /><!-- 用于配置浏览器页签+地址栏的颜色(仅支持安卓手机浏览器) --><meta name="theme-color" content="red" /><!-- 网站描述 --><meta name="description" content="Web site created using create-react-app" /><!-- 用于指定网页添加到手机主屏幕后的图标 --><link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /><!-- 应用加壳时的配置文件 --><link rel="manifest" href="%PUBLIC_URL%/manifest.json" /><title>React App</title></head><body><!-- 若浏览器不支持 js 则展示标签中的内容 --><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body>
</html>

样式的模块化

样式的模块化可用于解决样式冲突的问题。该方法比较麻烦,实际开发用的比较少。用 less 就能解决了。

component/Hello 文件下的 index.css 改名为 index.module.css

.title {background-color: orange;
}

Hello 组件导入样式:

import { Component } from 'react'
import hello from './index.module.css'export default class Hello extends Component {render() {return <h2 className={hello.title}>Hello,React!</h2>}
}

组件化开发思想

  1. 拆分组件、实现静态组件,注意:classNamestyle 的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的 state 中?
  • 某个组件使用:放在其自身的 state
  • 某些组件使用:放在他们共同的父组件 state 中,即状态提升
  1. 关于父子之间通信:
  • 父传子:直接通过 props 传递

  • 子传父:父组件通过 props 给子组件传递一个函数,子组件调用该函数

    //子组件
    this.props.XXX(函数)//父组件
    <子组件 xxx(函数名)={对应的处理函数}/>
    
  1. 结构命名

写成这种index形式,引入时只需要引入对应文件夹,react脚手架会自动找到里面的index.js / jsx

导入时 .js 跟 .jsx 后缀可以省略

TodoList 案例总结

// 父组件
class Father extends Component {state: {todos: [{ id: '001', name: '吃饭', done: true }],flag: true,}addTodo = (todo) => {const { todos } = this.stateconst newTodos = [todo, ...todos]this.setState({ todos: newTodos })}render() {return <List todos={this.state.todos} addTodo={this.addTodo} />}
}// 子组件
class Son extends Component {// 由于 addTodo 是箭头函数,this 指向父组件实例对象,因此子组件调用它相当于父组件实例在调用handleClick = () => {this.props.addTodo({ id: '002', name: '敲代码', done: false })}render() {return <button onClick={this.handleClick}>添加</button>}
}
// 键盘按起事件
onKeyUp = (event)=>{const {target,keyCode} = event// 判断用户是否敲下回车if(keyCode != 13) return// 判断用户输入的是否为空if(target.value.trim() == '') {alert("不能输入空")return}// 敲下回车将值传去父组件this.props.addList(target.value)// 随后清空input值target.value = ''}// 弹出对话框询问,需要添加windowif(window.confirm('是否删除'))
  1. 注意 defaultCheckedchecked 的区别,类似的还有:defaultValuevalue
  2. 拿复选框的值需要掉event.target.checked
  3. 状态在哪里,操作状态的方法就在哪里

React 网络请求

React 脚手架配置代理

方法一:

package.json 文件中进行配置:

"proxy": "http://localhost:5000"
  • 优点:配置简单,前端请求资源可不加前缀
  • 缺点:不能配置多个代理
  • 工作方式:当请求了 3000 端口号(本机)不存在的资源时,就会把请求转发给 5000 端口号服务器

方法二:

src 目录下创建代理配置文件 setupProxy.js ,进行配置:

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

消息订阅-发布机制

即 React 中兄弟组件任意组件之间的通信方式。

使用的工具库:PubSubJS(opens new window)

下载安装 PubSubJSnpm install pubsub-js --save

基础用法:

import PubSub from 'pubsub-js'// 订阅消息
var token = PubSub.subscribe('topic', (msg, data) => {console.log(msg, data)// msg 是订阅的名称,data才是发送的数据
})// 发布消息
PubSub.publish('topic', 'hello react')// 取消订阅
PubSub.unsubscribe(token)

Github 搜索框案例知识点总结

import axios from 'axios'
axios.get('url').then((res,err)=>{即可获得数据})// 只需在componentDidMount 掉用即可
  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
  2. ES6 知识点:解构赋值 + 重命名
let obj = { a: { b: 1 } }//传统解构赋值
const { a } = obj//连续解构赋值
const {a: { b },
} = obj//连续解构赋值 + 重命名
const {a: { b: value },
} = obj
  1. 消息订阅与发布机制
  • 先订阅,再发布(隔空对话)
  • 适用于任意组件间通信
  • 要在 componentWillUnmount 钩子中取消订阅
  1. fetch 发送请求(关注分离的设计思想)
try {// 先看服务器是否联系得上const response = await fetch(`/api1/search/users2?q=${keyWord}`)// 再获取数据const data = await response.json()console.log(data)
} catch (error) {console.log('请求出错', error)
}

React 路由

路由的理解

何为路由?

  • 一个路由是一个映射关系
  • key 为路径,value 可能是 function 或 组件

后端路由:

  • valuefunction ,用于处理客户端的请求
  • 注册路由:router.get(path, function(req, res))
  • 工作过程:Node 接收到请求,根据路径匹配路由,调用对应函数处理请求,返回响应数据

前端路由:

  • value 是组件
  • 注册路由:<Route path="/test" component={Test}>
  • 工作过程:浏览器路径变为 /test ,展示 Test 组件

路由基本使用

安装 react-router-dom

// 安装 5.X 版本路由
npm install react-router-dom@5.2.0 -S// 最新已经 6.X 版本,用法和 5.X 有所不同
npm install react-router-dom -S
  1. 导航区a标签改为Link 标签
<Link to='/about'>about</Link>
  1. 展示区写
<Routes><Route path="/about" element={<About />} />
</Routes>
  1. <App> 的最外侧包裹 <BrowserRouter><HashRouter>
import { BrowserRouter } from 'react-router-dom'
import App from './App'ReactDOM.render(<BrowserRouter><App /></BrowserRouter>,document.getElementById('root')
)

6.x 版本的用法参考文章(opens new window)

router v6 与router v5的区别

router v6的快速上手

5.x 版本为例展示基本使用:

// App.jsx
import React, { Component } from 'react'
import { Link, Route } from 'react-router-dom'
import Home from './components/Home'
import About from './components/About'export default class App extends Component {render() {return (<div><div className="list-group"><Link className="list-group-item" to="/about">About</Link><Link className="list-group-item" to="/home">Home</Link></div><div className="panel-body"><Route path="/about" component={About} /><Route path="/home" component={Home} /></div></div>)}
}

<App> 的最外侧包裹 <BrowserRouter><HashRouter>

// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter } from 'react-router-dom'
import App from './App'ReactDOM.render(<BrowserRouter><App /></BrowserRouter>,document.getElementById('root')
)

6.x 版本的用法

// App.jsx
import React, { Component } from 'react'
import { Routes,Route,Link } from 'react-router-dom';
import Home from './components/Home'
import About from './components/About'export default class App extends Component {render() {return (<div>// 控制路由的转换<Link className="list-group-item active" to='/home'>home</Link><Link className="list-group-item" to='/about'>about</Link>// 展示对应路由的内容// 与5.x 不同于 需要用Routes 包裹并更换component 为 element<Routes><Route path="/home" element={<Home />} /><Route path="/about" element={<About />} /></Routes></div>)}
}

<App> 的最外侧包裹 <BrowserRouter><HashRouter>

import React from "react";
import ReactDOM from 'react-dom'
import { BrowserRouter as Router } from 'react-router-dom';
import App from './App'ReactDOM.render(
<Router><App/>
</Router>,document.getElementById('root'))

路由组件和一般组件

写法不同:

  • 一般组件:<Demo/>
  • 路由组件:<Route path="/demo" component={Demo}/>

存放位置不同:

  • 一般组件:components
  • 路由组件:pages

接收到的 props 不同:

  • 一般组件:标签属性传递
  • 路由组件:接收到三个固定的属性
// 从this.props 里接受到的三个属性的主要参数
history:go: ƒ go(n)goBack: ƒ goBack()goForward: ƒ goForward()push: ƒ push(path, state)replace: ƒ replace(path, state)location:pathname: "/home/message/detail/2/hello"search: ""state: undefinedmatch:params: {}path: "/home/message/detail/:id/:title"url: "/home/message/detail/2/hello"

NavLink 的使用

NavLink 可以实现路由链接的高亮,通过 activeClassName 指定样式名,默认追加类名为 active

<NavLink activeClassName="demo" to="/about">About</NavLink><NavLink activeClassName="demo" to="/home">Home</NavLink>

封装 NavLink 组件:由于 NavLink 组件中重复的代码太多,因此进行二次封装。

※ 细节点:组件标签的内容会传递到 this.props.children 属性中,反过来通过指定标签的 children 属性可以修改组件标签内容

// MyNavLink 组件
import React, { Component } from 'react'
import { NavLink } from 'react-router-dom'export default class MyNavLink extends Component {render() {// this.props.children 可以取到标签内容,如 About, Home// 反过来通过指定标签的 children 属性可以修改标签内容return <NavLink activeClassName="demo" className="list-group-item" {...this.props} />}
}

封装完后使用

<MyNavLink to="/about">About</MyNavLink><MyNavLink to="/home">Home</MyNavLink>

Switch 的使用

Switch 可以提高路由匹配效率,如果匹配成功,则不再继续匹配后面的路由,即单一匹配。

<!-- 只会展示 Home 组件 -->
<Switch><Route path="/about" component={About} /><Route path="/home" component={Home} /><Route path="/home" component={Test} />
</Switch>

6.x 中switch 已更改用法(为Routes)

     <Routes><Route path="/home" element={<Home />} /><Route path="/about" element={<About />} /></Routes>

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

  • public/index.html 中 引入样式时不写 .// (常用)
  • public/index.html 中 引入样式时不写 ./%PUBLIC_URL% (常用)
  • 使用 HashRouter
<link rel="stylesheet" href="/css/bootstrap.css" /><link rel="stylesheet" href="%PUBLIC_URL%/css/bootstrap.css" />

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

  • 默认使用模糊匹配(输入的路径必须包含要匹配的路径,且顺序一致)
  • 开启严格匹配:<Route exact path="/about" component={About}/>
  • 严格匹配需要再开,开启可能会导致无法继续匹配二级路由,因为路由时按注册顺序去寻找的,当第一层开了严格匹配第一层的匹配失败就不会找第二层了

Redirect 的使用

  • 一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到 Redirect 指定的路由
import {Redirect} from 'react-router-dom'
<Switch><Route path="/about" component="{About}" /><Route path="/home" component="{Home}" />// 放在最后面<Redirect to="/about" />
</Switch>

6.x 中已移除Redirect

import { Routes,Route,Navigate } from 'react-router-dom';
<Routes><Route path="/" element={<Navigate replace to="/home" />} />
</Routes>

嵌套路由

  • 注册子路由需写上父路由的 path
  • 路由的匹配是按照注册路由的顺序进行的
<!-- 父组件 -->
<MyNavLink to="/about">About</MyNavLink>
<MyNavLink to="/home">Home</MyNavLink><Switch><Route path="/about" component={About} /><Route path="/home" component={Home} /><Redirect to="/about" />
</Switch>
<!-- 子组件 -->
<ul className="nav nav-tabs"><li><MyNavLink to="/home/news">News</MyNavLink> </li><li><MyNavLink to="/home/message">Message</MyNavLink></li>
</ul>
<!-- 路由展示区域 -->
<Switch><Route path="/home/news" component={News} /><Route path="/home/message" component={Message} /><Redirect to="/home/news" />
</Switch>

动态路由传参

三种方式:params, search, state 参数

三种方式对比:

  • state 方式当前页面刷新可保留参数,但在新页面打开不能保留。前两种方式由于参数保存在 URL 地址上,因此都能保留参数。
  • paramssearch 参数都会变成字符串

第一种 params 传参

<!-- 路由链接 -->
<Link to={`/home/mess/details/${item.id}/${item.name}`}>{item.name}</Link>
<!-- 注册路由 -->
<Route path='/home/mess/details/:id/:title' component={Details}></Route>
//接收参数
const { id, title } = this.props.match.params

第二种 search 传参

<!-- 路由链接 声明search 参数传递参数-->
<Link to={`/home/mess/details?id=${item.id}&title=${item.name}`}>{item.name}</Link>
<!-- 注册路由 声明search 参数传递参数  无需特殊声明-->
<Route path='/home/mess/details' component={Details}></Route>
//接收参数
// 解析 urlenencode的函数
const app = (str)=>{const search = this.props.location.searchvar query = search.substring(1);var vars = query.split("&");for (var i=0;i<vars.length;i++) {var pair = vars[i].split("=");if(pair[0] == str){return pair[1];}}return (false)}
const id = app('id')   // 得到id 的value
const title = app('title') // 得到 title 的value
// 可以获得对应id 的item对象
const findRes = data.find(item=>{return item.id == id})
// 或者引入 import qs from 'querystring'
import qs from 'querystring'  // qs.parse 可以转urlencoded 为对象形式  qs.stringfy() 可以转为urlencoded形式
const { search } = this.props.location
const { id, title } = qs.parse(search.slice(1))

第三种 state 传参

<!-- 路由链接 声明 state 参数传递参数-->
<Link to={{pathname:"/home/mess/details",state:{id:item.id,title:item.name}}}>{item.name}</Link>
<!-- 注册路由 声明search 参数传递参数  无需特殊声明-->
<Route path='/home/mess/details' component={Details}></Route>
//state接收参数
// 接受state 参数
const {id,title} = this.props.location.state

编程式导航

编程式导航是使用路由组件 this.props.history 提供的 API 进行路由跳转:(只有路由组件才有该方法)

this.props.history.push(path, state)
this.props.history.replace(path, state)
this.props.history.goForward()
this.props.history.goBack()
this.props.history.go(n)// 调用  路由组件 porps 的方法
pushShow(id,title){this.props.history.push(`/home/mess/details/${id}/${title}`)}
// 绑定 编程式导航的方法
<button onClick={()=>this.pushShow(item.id,item.name)}>push</button>// 编程式导航传参
this.props.history.push(`/home/message/detail/${id}/${title}`)
this.props.history.push(`/home/message/detail?id=${id}&title=${title}`)
this.props.history.push(`/home/message/detail`, { id: id, title: title })

withRouter 的使用

withRouter 的作用:加工一般组件,让其拥有路由组件的 API ,如 this.props.history.push 等。

import React, {Component} from 'react'
import {withRouter} from 'react-router-dom'class Header extends Component {...
}
// 导出时用withRouter加工后的组件
export default withRouter(Header)

BrowserRouter 和 HashRouter

底层原理不一样:

  • BrowserRouter 使用的是 H5 的 history API,不兼容 IE9 及以下版本。
  • HashRouter 使用的是 URL 的哈希值。

路径表现形式不一样

  • BrowserRouter 的路径中没有 # ,如:localhost:3000/demo/test
  • HashRouter 的路径包含#,如:localhost:3000/#/demo/test

刷新后对路由 state 参数的影响

  • BrowserRouter 没有影响,因为 state 保存在 history 对象中。
  • HashRouter 刷新后会导致路由 state 参数的丢失!

备注:HashRouter 可以用于解决一些路径错误相关的问题。


React UI 组件库

Ant Design 配置按需引入和自定义主题

以下配置是 3.x 版本,4.x 版本见官网(opens new window)

1、安装依赖:

npm install react-app-rewired customize-cra babel-plugin-import  // 处理按需导入
npm install less less-loader  // 自定义主题文件

2、修改 package.json

"scripts": {"start": "react-app-rewired start","build": "react-app-rewired build","test": "react-app-rewired test","eject": "react-scripts eject"
}

3、根目录下创建 config-overrides.js

//配置具体的修改规则
const { override, fixBabelImports, addLessLoader } = require('customize-cra')module.exports = override(fixBabelImports('import', { // 组件按需导入无需再导入整个包libraryName: 'antd',libraryDirectory: 'es',style: true,}),addLessLoader({lessOptions: {   // 从less5.x以上需要套上lessOptions才能配置文件javascriptEnabled: true,modifyVars: { '@primary-color': 'green' },//  自定义全局主题颜色为绿色},})
)

详细用法前去官方文档


Redux

官网(opens new window)

中文文档(opens new window)

Redux 概述

Redux 为何物

  • Redux 是用于做 状态管理 的 JS 库
  • 可用于 React、Angular、Vue 等项目中,常用于 React
  • 集中式管理 React 应用多个组件共享的状态

何时用 Redux

  • 某个组件的状态,需要让其他组件拿到(状态共享)
  • 一个组件需要改变另一个组件的状态(通信)
  • 使用原则:不到万不得已不要轻易动用

Redux 工作流程

  • 组件想操作 Redux 中的状态:把动作类型和数据告诉 Action Creators
  • Action Creators 创建 action :同步 action 是一个普通对象,异步 action 是一个函数
  • Store 调用 dispatch() 分发 actionReducers 执行
  • Reducers 接收 previousStateaction 两个参数,对状态进行加工后返回新状态
  • Store 调用 getState() 把状态传给组件

完整版 redux 的使用

1、 创建 redux 文件夹下的 store.js

// store.js
// 导入创建store方法,跟处理action异步函数的方法
import {createStore,applyMiddleware} from 'redux'
// 导入需要管理的组件
import count from './count_reducer'
// 引入处理异步函数的中间件
import thunk from 'redux-thunk'// 导出 store  第一个参数为需要管理的组件,第二个参数为中间件
export default createStore(count,applyMiddleware(thunk))

2、 创建 redux 文件夹下的 count_reducer.js (用于指定操作状态值)

// 赋初始值时为0
const InitState = 0
//  第一次action里的  type 为 undefined ,avalue 为空 调用函数进行初始化状态 返回初始状态为0
// 向外暴露一个方法
export default function count (preState=InitState,action){//  从action 里解构赋值出要用的type跟stateconst {type,state} = actionswitch (type) {case 'increment':  //  加法return preState + state*1case 'decrement': // 减法return preState - state*1default:return preState}
}

3、 创建 redux 文件夹下的 count_action.js (用于创建action对象 / 异步函数)

// 用来给count 创建action对象
// 返回的是一个对象
export const actionIncrement = (state)=>({type:'increment',state})
export const actionDecrement = (state)=>({type:'decrement',state})// 创建异步action对象
export const IncrementSync = (state,time)=>{// 异步api 不同在于返回一个函数给store 然后会传一个dispatch参数return (dispatch)=>{setTimeout(() => {dispatch(actionIncrement(state))}, time);}
}

4、 在需要使用的地方引入

import React, { Component } from 'react'
// 引入stroe
import store from './store/store'
// 引入action创建对象
import {actionIncrement,actionDecrement,IncrementSync} from './store/count_action'
export default class App extends Component {//  监听store里的属性变化后重新调用render渲染画面componentDidMount(){store.subscribe(()=>{this.setState({})})}
// 加+1 的处理函数AddNum = ()=>{const {value} = this.allNum// const {count} = this.state// this.setState({count:count+value*1})  原生写法// store.dispatch({type:'increment',state:value}) 简写版不用action 直接dispatch去调用reducer// 用action 完整版写法  store.dispatch(actionIncrement(value))}AddNumSync = ()=>{const {value} = this.allNum// const {count} = this.state// setTimeout(() => {// store.dispatch({type:'increment',state:value})// 调用action异步方法store.dispatch(IncrementSync(value,500))// }, 500);}render() {return (<div>// store.getState() 获取到当前的count值<h1>当前求和数:{store.getState()}</h1><select ref = {c => this.allNum = c}><option value='1'>1</option><option value='2'>2</option><option value='3'>3</option></select><button onClick={this.AddNum}>+</button><button onClick={this.AddNumSync}>异步加</button></div>)}
}

核心概念(api)

action

  • 表示动作的对象,包含 2 个属性
  • type :标识属性,值为字符串,唯一,必须属性
  • data :数据属性,类型任意,可选属性
  • {type: 'increment', data: 2}

reducer

  • 用于初始化状态、加工状态
  • 根据旧状态和 action 产生新状态
  • 纯函数

纯函数:输入同样的实参,必定得到同样的输出

  • 不能改写参数数据
  • 不产生副作用,如网络请求、输入输出设备(网络请求不稳定)
  • 不能调用 Date.now()Math.random() 等不纯方法

store

  • Redux 核心对象,内部维护着 statereducer
  • 核心 API
    • store.getState() :获取状态
    • store.dispatch(action) :分发任务,触发 reducer 调用,产生新状态
    • store.subscribe(func) :注册监听函数,当状态改变自动调用

combineReducers()

作用:合并多个reducer函数

//代码示例
------------------ redux/reducers/index.js ------------------------------------
/*** 该文件用于汇总所有的reducer为一个总的reducer*/
//引入combineReducers,用于汇总多个reducer
import {combineReducers} from 'redux'
//引入为Count组件服务的reducer
import count from './count'
import persons from './person'//汇总所有的reducer变为一个总的reducer
export default combineReducers({count,persons
})

注意事项

  • redux 只负责管理状态,状态改变驱动页面展示要自己写
  • 可以在 index.js 中统一监听状态变化,也可以在组件中单独监听。注意不能直接 this.render() 调用 render 函数,要通过 this.setState({}) 间接调用
  • reducerstore 自动触发首次调用,传递的 preStateundefinedaction{type: '@@REDUX/ININT_a.5.v.9'} 类似的东东,只有 type

Redux 异步编程

安装异步中间件:

npm install redux-thunk -S

要点:

  • 延迟的动作不想交给组件,而是 action
  • 当操作状态所需数据要靠异步任务返回时,可用异步 action
  • 创建 action 的函数返回一个函数,该函数中写异步任务
  • 异步任务完成后,分发一个同步 action 操作状态
  • 异步 action 不是必要的,完全可以在组件中等待异步任务结果返回在分发同步 action
// store.js
/*** 该文件撰文用于暴露一个store对象,整个应用只有一个store对象*/
//引入createStore,专门用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
//引入汇总后的reducer
import reducer from './reducers'
//引入redux-thunk,用于支持异步action
import thunk from 'redux-thunk'
//引入redux-devtools-extension
import {composeWithDevTools} from 'redux-devtools-extension'
//暴露store
export default createStore(reducer,composeWithDevTools(applyMiddleware(thunk)))
// count_action.js
import { INCREMENT, DECREMENT } from './constant.js'export const createIncrementAction = (data) => ({ type: INCREMENT, data })
export const createDecrementAction = (data) => ({ type: DECREMENT, data })// 异步 action 返回一个函数
export const createIncrementAsyncAction = (data, time) => {return (dispatch) => {setTimeout(() => {dispatch(createIncrementAction(data))}, time)}
}
// Count.jsx
incrementAsync = () => {const { value } = this.selectNumberstore.dispatch(createIncrementAsyncAction(value * 1))
}

整个过程简单理解:store 在分发 action 时,发现返回一个函数,那它知道这是个异步 action 。因此 store 勉为其难地帮忙执行这个函数,同时给这个函数传递 dispatch 方法,等待异步任务完成取到数据后,直接调用 dispatch 方法分发同步 action

React-Redux

React-Redux 是一个插件库,用于简化 React 中使用 Redux 。

React-Redux 将组件分为两类:

  • UI 组件

    • 只负责 UI 呈现,不带有业务逻辑
    • 通过 props 接收数据
    • 不能使用 Redux 的 API
    • 保存在 components 文件夹下
  • 容器组件
    • 负责管理数据和业务逻辑,和 Redux 通信,将结果交给 UI 组件
    • 可使用 Redux 的 API
    • 保存在 containers 文件夹下

相关API

① Provider

作用: 让所有组件都可以得到state数据

import React from 'react'
import ReactDOM from "react-dom"
import App from './App'
import store from './redux/store'
import {Provider} from 'react-redux'ReactDOM.render(/* 此处需要用Provider包裹App,目的是让App所有的后代容器组件都能接收到store */<Provider store={store}><App/></Provider>,document.getElementById('root')
)

connect()()

  1. 作用: 用于包装 UI 组件生成容器组件
  2. 使用connect(mapDispatchToProps,mapDispatchToProps)(UI组件)

注意点:

  1. 该方法默认传入statedispatch
  2. 可以省略dispatch直接传入action方法,该api会自动帮你调用dispatch
Ⅰ-mapStateToProps

作用:将外部的数据(即state对象)转换为UI组件的标签属性

1.mapStateToProps函数返回的是一个对象;

2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value

3.mapStateToProps用于传递状态

function mapStateToProps(state){return {count:state}
}
Ⅱ-mapDispatchToProps

作用:将分发action的函数转换为UI组件的标签属性

  1. mapDispatchToProps函数返回的是一个对象;
  2. 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
  3. mapDispatchToProps用于传递操作状态的方法
  4. 可以省略dispatch,直接传入action,api将会自动调用dispatch
Ⅲ-代码示例
------------------------------不简化代码-----------------------------------------------
/* 1.mapStateToProps函数返回的是一个对象;2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value3.mapStateToProps用于传递状态
*/
function mapStateToProps(state){return {count:state}
}/* 1.mapDispatchToProps函数返回的是一个对象;2.返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value3.mapDispatchToProps用于传递操作状态的方法
*/
function mapDispatchToProps(dispatch){return {jia:number => dispatch(createIncrementAction(number)),jian:number => dispatch(createDecrementAction(number)),jiaAsync:(number,time) => dispatch(createIncrementAsyncAction(number,time)),}
}//使用connect()()创建并暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)----------------下面是简化代码-----------------------------
//使用connect()()创建并暴露一个Count的容器组件
//使用connect(传入状态,操作状态方法)(UI组件)
export default connect(state => ({count: state.count,personCount: state.persons.length}),{increment, decrement, incrementAsync}
)(Count)

多个组件数据共享

首先规范化文件结构,容器组件和 UI 组件合为一体后放在 containers 文件夹。redux 文件夹新建 actionsreducers 文件夹分别用于存放每个组件对应的 actionreducer

添加案列

新建 Person 组件对应的 actionreducer


--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//创建增加一个人的action动作对象
export const addPerson=personObj=>({type:ADD_PERSON,data:personObj
})

--------------------------------------person.js-------------------------------
import {ADD_PERSON} from '../constant'
//初始化人的列表
const initState = [{id:'001',name:'tom',age:18}]
export default function personReducer(preState=initState,action){// console.log('personReducer@#@#@#');const {type,data} = actionswitch (type) {case ADD_PERSON: //若是添加一个人//preState.unshift(data) //此处不可以这样写,这样会导致preState被改写了,personReducer就不是纯函数了。return [data,...preState]default:return preState}
}

关键步骤:在 store.js 中使用 combineReducers() 整合多个 reducer 来创建 store 对象。

这样 Redux 中就以对象的形式存储着每个组件的数据。

// redux/store.jsimport { createStore, applyMiddleware, combineReducers } from 'redux'
import countReducer from './reducers/count'
import personReducer from './reducers/person'
import thunk from 'redux-thunk'//  这样就可以同时管理多个组件的状态
const Reducers = combineReducers({total: countReducer,personList: personReducer,
})export default createStore(Reducers, applyMiddleware(thunk))

Person 组件中获取 Redux 保存的状态,包括其他组件的数据。

import React, { Component } from 'react'
import { connect } from 'react-redux'
import { createAddPersonAction } from '../../redux/actions/person'
import { nanoid } from 'nanoid'class Person extends Component {addPerson = () => {const name = this.nameInput.valueconst age = this.ageInput.valueconst personObj = { id: nanoid(), name, age }this.props.addPerson(personObj)this.nameInput.value = ''this.ageInput.value = ''}render() {return (<div><h2>在Person组件拿到Count组件的数据:{this.props.count}</h2><input type="text" ref={(c) => (this.nameInput = c)} placeholder="Please input name" /><input type="text" ref={(c) => (this.ageInput = c)} placeholder="Please input age" /><button onClick={this.addPerson}>添加</button><ul>{this.props.personList.map((item) => {return (<li key={item.id}>{item.name} -- {item.age}</li>)})}</ul></div>)}
}export default connect(// state 是 Redux 保存的状态对象// 容器组件从 Redux 中取出需要的状态,并传递给 UI 组件state => ({personList: state.personList, count: state.total}),{addPerson: createAddPersonAction// 这一行凑数的,为了保持代码格式addPerson2: createAddPersonAction}
)(Person)

项目打包运行

运行命令:npm run build 进行项目打包,生成 build 文件夹存放着打包完成的文件。

运行命令:npm i serve -g 全局安装 serve ,它能够以当前目录为根目录开启一台服务器,进入 build 文件夹所在目录,运行 serve 命令即可开启服务器查看项目效果。

或者 serve build 运行指定文件夹 + port 可以指定端口等

React 扩展内容

setState 更新状态的两种写法

对象式:setState(stateChange, [callback])

  • stateChange 为状态改变对象(该对象可以体现出状态的更改)
  • callback 是可选的回调函数, 它在状态更新完毕、界面也更新后才被调用

函数式:setState(updater, [callback])

  • updater 为返回 stateChange 对象的函数。
  • updater 可以接收到 state 和 props。

说明:

  • React 状态更新是异步的。下述代码打印的 count 值是上一次的值,而非更新后的。可在第二个参数回调中获取更新后的状态。
add = () => {this.setState({ count: this.state.count + 1 })console.log(this.state.count)
}add = () => {this.setState({ count: this.state.count + 1 }, () => {console.log(this.state.count)})
}
  • callback 回调在 componentDidMount 钩子之后执行
  • 对象式写法可以看做函数式写法的语法糖
add = () => {this.setState((state, props) => {return { count: state.count + props.step }})
}
this.setState({ count: this.state.count + 1 })

路由组件懒加载 lazyLoad

import React, { Component, lazy, Suspense } from 'react'
import Loading from './Loading'// 通过 lazy 函数配合 import() 函数动态加载路由组件
// 路由组件代码会被分开打包
const Home = lazy(() => import('./Home'))
const About = lazy(() => import('./About'))export default Demo extends Component {render() {return (<div><h1>Demo 组件</h1><Link to="/home">Home</Link><Link to="/about">About</Link>// 通过 <Suspense> 指定在加载得到路由打包文件前显示一个自定义 Loading 界面<Suspense fallback={Loading}><Switch><Route path="/home" component={Home}><Route path="/about" component={About}></Switch></Suspense></div>)}
}

React Hook

Hook 是 React 16.8.0 增加的新特性,让我们能在函数式组件中使用 state 和其他特性

State Hook

  • State Hook 让函数式组件也可拥有 state 状态。
  • 语法:const [Xxx, setXxx] = React.useState(initValue)
  • useState() 参数:状态初始化值;返回值:包含 2 个元素的数组,分别为状态值和状态更新函数
  • setXxx() 的 2 种用法:
    • setXxx(newValue)
    • setXxx(value => newValue)
    • 注意!新状态值会覆盖原状态值!因此若有多个状态,只能多次调用 React.useState ,不能使用对象!
const [count, setCount] = React.useState(0)
const [name, setName] = React.useState('Tom')function add() {setCount(count + 1)setCount((count) => count + 1)
}

Effect Hook

  • Effect Hook 让我们能在函数式组件中执行副作用操作(就是模拟生命周期钩子)
  • 副作用操作:发送 Ajax 请求、定时器、手动更改真实 DOM
  • Effect Hook 可以模拟三个钩子:componentDidMountcomponentDidUpdatecomponentWillUnmount
  • React.useEffect 第一个参数 return 的函数相当于 componentWillUnmount ,若有多个会按顺序执行
// 语法
React.useEffect(() => {...return () => {// 组件卸载前执行,即 componentWillUnmount 钩子...}
}, [stateValue]) //  [stateValue] 为监视state的某个属性// 模拟 componentDidMount
// 第二个参数数组为空,表示不监听任何状态的更新
// 因此只有页面首次渲染会执行输出
React.useEffect(() => {console.log('DidMount')return () => {console.log('WillUnmount 1')}
}, [])// 模拟全部状态 componentDidUpdate
// 若第二个参数不写,表示监听所有状态的更新
React.useEffect(() => {console.log('All DidUpdate')return () => {console.log('WillUnmount 2')}
})// 模拟部分状态 componentDidUpdate
// 第二个参数数组写上状态,表示只监听这些状态的更新
React.useEffect(() => {console.log('Part DidUpdate')return () => {console.log('WillUnmount 3')}
}, [count, name])// 若调用 ReactDOM.unmountComponentAtNode(document.getElementById('root'))
// 会输出 WillUnmount 1、2、3

Ref Hook

  • Ref Hook 可以在函数式组件存储或查找组件内的标签或其他数据
  • 语法:const refContainer = React.useRef()
  • 保存标签对象的容器,和 React.createRef() 类似,也是专人专用
function Demo() {const myRef = React.useRef()function show() {console.log(myRef.current.value)}return (<div><input type="text" ref={myRef} /><button onClick={show}>展示数据</button></div>)
}

Fragment

  • Fragment 标签本身不会被渲染成一个真实 DOM 标签,有点像 Vue 的 template
  • 用空标签也有相同效果,但是空标签不能传递任何属性,Fragment 标签可以传递 key 属性,遍历时候可用。
import React, { Component, Fragment } from 'react'export default class Demo extends Component {render() {return (<Fragment key={1}><input type="text" /><input type="text" /></Fragment>)// 或return (<><input type="text" /><input type="text" /></>)}
}

Context

Context 是一种组件间通信方式,常用于祖父组件与子孙组件。实际开发一般不用,一般用 React-Redux

用法说明:

1) 创建Context容器对象:
const XxxContext = React.createContext()2) 渲染子组时,外面包裹xxxContext.Provider, 通过value属性给后代组件传递数据:
<XxxContext.Provider value={数据}>子组件
</XxxContext.Provider>3) 后代组件读取数据:// 第一种方式:仅适用于类组件
// 声明接收context
static contextType = xxxContext
// 读取context中的value数据
this.context//第二种方式: 可用于函数组件与类组件
<XxxContext.Consumer>{// value就是context中的value数据value => (...)}
</XxxContext.Consumer>

举个栗子:

// context.jsimport React from 'react'
export const MyContext = React.createContext()
export const { Provider, Consumer } = MyContext// A.jsxclass A extends Component {state = { username: 'tom', age: 18 }render() {const { username, age } = this.statereturn (<div><h3>A组件</h3><h4>用户名是:{username}</h4><Provider value={{ username, age }}><B /></Provider></div>)class B extends Component {render() {return (<div><h3>B组件</h3><C /></div>)// 后代c 要使用祖组件传来的value
class C extends Component {// 先声明接受static contextType = MyContextrender() {//  然后就可以从this。context上读取传来的值,不声明就没有值const { username, age } = this.contextreturn (<div><h3>C组件</h3><h4>从A组件接收到的用户名:{username},年龄:{age}</h4></div>)
//  函数式组件使用
function C() {return (<div><h3>我是C组件</h3><h4>从A组件接收到的用户名:// 在要使用的地方 用Consumer 包裹住,里面传一个value value里既有传来的属性<Consumer>{(value) => `${value.username},年龄是${value.age}`}</Consumer></h4></div>)

组件渲染优化

问题:

  • 只要调用 setState() ,即使没有修改状态,组件也会重新 render()
  • 只要父组件重新渲染,即使子组件没有使用父组件的状态,也会重新 render()

原因:

  • shouldComponentUpdate() 钩子默认总是返回 true

改进:

  • 只有组件的 stateprops 的数据发生改变时才重新渲染

方式:

  1. 手动重写 shouldComponentUpdate(nextProps, nextState) 的逻辑,只有数据发生改变才返回 true
  2. 使用 PureComponent ,它重写了 shouldComponentUpdate() , 只有 stateprops 数据有变化才返回 true

TIP

  • 它只是进行 stateprops 数据的浅比较, 如果只是数据对象内部数据变了, 返回 false。即对于引用数据类型,比较的是地址引用
  • 不要直接修改 state 数据, 而是要产生新数据
import React, { PureComponent } from 'react'class Demo extends PureComponent {...addStu = () => {// 不会渲染const { stus } = this.statestus.unshift('小刘')this.setState({ stus })// 重新渲染const { stus } = this.statethis.setState({ stus: ['小刘', ...stus] })}...
}

render props (插槽)

类似于 Vue 中的插槽技术

如何向组件内部动态传入带内容的结构(即标签或组件)?

  • Vue:插槽技术
  • React:
    • 使用 children props:通过组件标签体传入结构
    • 使用 render props:通过组件标签属性传入结构,可携带数据

children props 方式:

  • 组件标签体内容会存储到 this.props.children
  • 缺点:A 组件无法向 B 组件传递数据

使用 children props

import React, { Component } from 'react'export default class Parent extends Component {render() {return (<div><h3>Parent组件</h3><A><B /></A></div>)}
}class A extends Component {state = { name: 'tom' }render() {return (<div><h3>A组件</h3>{this.props.children}</div>)}
}class B extends Component {render() {return (<div><h3>B组件</h3></div>)}
}

render props 方式:

  • <A render={(name) => <B name={name} />} />
  • {this.props.render(name)}
import React, { Component } from 'react'export default class Parent extends Component {render() {return (<div><h3>Parent组件</h3>// 调用一个render 内联一个函数携带数据参数,并返回一个组件标签 传递参数// 可以将B换成任意想展示的组件或内容<A render={(name) => <B name={name} />} /></div>)}
}class A extends Component {state = { name: 'tom' }render() {const { name } = this.statereturn (<div><h3>A组件</h3>// 在指定位置用了 this。props。render(xxx)既可以指定传来的组件展示的地方//  并将属性传递给B组件{this.props.render(name)}</div>)}
}class B extends Component {render() {return (<div><h3>B组件,{this.props.name}</h3></div>)}
}

错误边界

TIP

错误边界(Error boundary):用来捕获后代组件错误,渲染出备用页面。

注意:只在生产环境(项目上线)起效

特点:

  • 只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
  • 简单理解就是只能捕获后代组件生命周期钩子里面代码的错误
import React, { Component } from 'react'
import Child from './Child'export default class Parent extends Component {state = {//用于标识子组件是否产生错误hasError: '',}// 当子组件出现错误,会触发调用,并携带错误信息static getDerivedStateFromError(error) {// render 之前触发// 返回新的 statereturn { hasError: error }}componentDidCatch(error, info) {console.log(error, info)console.log('此处统计错误,反馈给服务器')}render() {return (<div><h2>Parent组件</h2>{this.state.hasError ? <h2>网络不稳定,稍后再试</h2> : <Child />}</div>)}
}

组件通信方式总结

  • props
  • 消息订阅发布:pubs-sub
  • 集中管理:Redux、dva 等
  • conText(祖孙之间,后代组件)

推荐搭配:

  • 父子组件:props
  • 兄弟组件:消息订阅-发布、集中式管理
  • 祖孙组件(跨级组件):消息订阅-发布、集中式管理、conText(开发用的少,封装插件用的多即 React-Redux)

自定义 Hook

注意事项

  • Hook 不处理的话规定只能在函数式组件中使用

你想在项目中使用基于类的 Hook 逻辑,并且目前无法将这些类组件重写为 Hooks。类可能太复杂了,或者如果你更改它,可能会破坏项目中的许多其他内容。这种方法的商业价值也值得怀疑。如果你转至 React 文档,会看到一个有趣的声明:

解决不能在类式组件里使用hook的方法

Using Hooks as HOC

HOC 是重用组件逻辑的高级 React 技术,其使我们能够在现有类组件中使用 Hook 逻辑。因为 HOC 是使一个组件作为输入,并通过一些额外的 props 返回相同的组件。在我们的情况下,我们将传递 Hook 函数作为 props。

import React from 'react';
import { useScreenWidth } from '../hooks/useScreenWidth';export const withHooksHOC = (Component: any) => {return (props: any) => {const screenWidth = useScreenWidth();return <Component width={screenWidth} {...props} />;};
};
复制代码

最后一步是用该HOC简单包装我们现有的类组件。然后,我们仅使用width属性作为传递给组件的其他属性。

import React from 'react';
import { withHooksHOC } from './withHooksHOC';interface IHooksHOCProps {width: number;
}class HooksHOC extends React.Component<IHooksHOCProps> {render() {return <p style={{ fontSize: '48px' }}>width: {this.props.width}</p>;}
}export default withHooksHOC(HooksHOC);

获取窗口视口大小

import React,{ useState , useEffect , useCallback ,Component} from 'react'function useWinsize(){//返回值:包含 2 个元素的数组,分别为状态值和状态更新函数const [size,SetSize] = useState({// 参数为初始值width:document.documentElement.clientWidth,height:document.documentElement.clientHeight,})// useCallback  用于缓存方法   useMemo 用于缓存属性 状态const onResize = useCallback(()=>{//  在此调用方法可以将方法缓存起来提高效率SetSize({//  更新数据,直接传newValue 值第一种更新用法width:document.documentElement.clientWidth,height:document.documentElement.clientHeight})},[]) //  第二个参数传空数组,表示didMount  只执行一次useEffect(()=>{//  当didmount 时执行一遍添加一个窗口监听事件,当  触发resize 事件时,执行 onResize 的回调函数更新窗口window.addEventListener('resize',onResize)//  调用unMount 组件卸载时移除组件监听事件,防止在其他组件继续监听return ()=>{window.removeEventListener('resize',onResize)}},[])// 调用该函数返回size 给使用者return size
}
//组件中使用
export default function MyHooks (){const size = useWinsize()console.log(size);
return <div>size:{size.width}x{size.height}</div>
}// App.jsx ----------------------------------------
import React, { Component } from 'react'// 使用Hook
import Win from './react_hook/UseClientHeigth'export default class App extends Component {render() {return (<div><Win/></div>)}
}

点击按钮更改名字,并展示延迟等待

import React, { useState, useEffect } from 'react'const usePerson = (name) => {const [loading, setLoading] = useState(true)
const [person, setPerson] = useState({})useEffect(() => {setLoading(true)setTimeout(()=> {setLoading(false)setPerson({name})},2000)},[name])return [loading,person]
}const AsyncPage = ({name}) => {const [loading, person] = usePerson(name)return (<>{loading?<p>Loading...</p>:<p>{person.name}</p>}</>)}const PersonPage = () =>{const [state, setState]=useState('')const changeName = (name) => {setState(name)}return (<><AsyncPage name={state}/><button onClick={() => {changeName('名字1')}}>名字1</button><button onClick={() => {changeName('名字2')}}>名字2</button></>)
}export default PersonPage

获取鼠标视口

import React, { useState, useEffect } from 'react'const useMousePosition = () => {const [position, setPosition] = useState({x: 0, y: 0 })useEffect(() => {const updateMouse = (e) => {setPosition({ x: e.clientX, y: e.clientY })}document.addEventListener('mousemove', updateMouse)return () => {document.removeEventListener('mousemove', updateMouse)}})return position
}export default useMousePosition// 需要引入时
import React, { Component } from 'react'// 使用Hook
import useMousePosition  from './react_hook/useMouse'// 注意只能在函数里使用
export default function App(){const position = useMousePosition()return (<div>{position.x},{position.y}</div>)
}

React 全家桶(react脚手架 redux react-redux react-router-dom ui库 reactHook)含 自定义hook的方法及使用相关推荐

  1. 视频教程-React 全家桶从入门到实战到源码-其他

    React 全家桶从入门到实战到源码 上市公司前端开发工程师,专注于 React 技术栈,对 React 全家桶从 react-router 路由到 Redux 状态管理工具再到 webpack 打包 ...

  2. react全家桶都有什么

    react全家桶都有:1.react是核心:2.redux相当于数据库:3.React Router是专为React设计的路由解决方案:4.axios用于浏览器和Node js的http客户端:5.A ...

  3. react全家桶从0到1(react-router4、redux、redux-saga)

    本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redux-saga的核心配置会加以讲解, ...

  4. React全家桶(技术栈) redux 代码

    React全家桶(技术栈) redux 代码 压缩包下载 https://download.csdn.net/download/qq_42740465/87629665 README.md ## 1. ...

  5. react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)

    react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redu ...

  6. 小邵教你玩转Typescript、ts版React全家桶脚手架

    前言:大家好,我叫邵威儒,大家都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑,从大学买的第一本vb和自学vb,我就与编程结下不解之缘,随后自学易语言写游戏辅助.交易软件,至今进入了前端领域, ...

  7. 【学习笔记】React+React全家桶学习笔记

    文章目录 1 为什么要使用React 2 React的定义 3 React的三大特性 4 React入门 4.1 hello_react 4.2 虚拟DOM的创建 4.3 JSX 4.4 模块与组件, ...

  8. 技术胖的2019新版React全家桶免费视频(84集)

    技术胖 2019年09月18日 阅读 29883 关注 技术胖的2019新版React全家桶免费视频(84集) 一共84集,从5月4日开始录制,到9月18日完成,5个月时间.如果是一个专业讲师,这进度 ...

  9. react全家桶实战(千峰教育)

    说明:本笔记为本人基于千锋教育2022版React全家桶教程_react零基础入门到项目实战完整版的学习笔记,知识点不清或不全,可以到视频教程中学习 文章目录 一.安装create-react-app ...

最新文章

  1. mysql 使用位运算
  2. 【TX2】TX2开发板系统默认串口有ttyS0(调试口)、ttyTHS1、ttyTHS2、ttyTHS3,通过修改设备树文件,可以新增三个串口
  3. 一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之登录窗口调试...
  4. win10下输入法突然变成繁体了怎么设置回来?(繁體)(快捷鍵:ctrl + shift +f)
  5. 洛谷 [SDOI2015]约数个数和 解题报告
  6. 用户暴增下的收入降低,AWS面临尴尬
  7. 吴恩达《机器学习》 --- 神经网络
  8. 快照(Snapshot)
  9. [转载] python全局变量的使用
  10. mysql_fetch_row()获取显示数据
  11. Spring Cloud(Greenwich版)-05-Spring Cloud集成Ribbon(客户端负载均衡组件)
  12. Ubuntu16.0.4 通过Docker安装酷Q
  13. 关于一些初级ACM竞赛题目的分析和题解(八)
  14. 百度api翻译html,帮助文档首页
  15. 大数据平台的SQL查询引擎有哪些—SparkSQL
  16. to_param()函数和parameterize()函数
  17. hp 交换机远程连接_Hp服务器 iLO3 使用方法
  18. datetime的时值
  19. 创建自己第一个安卓程序_从一天创建和发布我的第一个应用程序中学到的东西...
  20. 人脸检测论文:FaceBoxes: A CPU Real-time Face Detector with High Accuracy及其Pytorch实现

热门文章

  1. 画一个椭圆c语言程序,画椭圆 - c代码库 - 云代码
  2. 一位资深开发的个人经历(走心好文)
  3. 【NLP】一种自写的分词算法-中文基于字,英文基于单词,支持自定义字典
  4. 带你了解现在的LED显示屏技术
  5. 猿创征文|我的Go成长之路道阻且长
  6. 异常信息:test02FlexController”不存在,wufa
  7. Vue.js 实现简易购物车(商品的增加删除,价格的小计和总计)
  8. MATLAB对一子数组赋值
  9. 【排序】冒泡排序与快速排序(三个版本+非递归图示详解哦)
  10. linux ksoftirqd进程,ksoftirqd进程导致cpu消耗殆尽