React 全家桶(react脚手架 redux react-redux react-router-dom ui库 reactHook)含 自定义hook的方法及使用
文章目录
- 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:
- 本质是 Object 类型的对象(一般对象)
- 虚拟 DOM 比较“轻”,真实 DOM 比较“重”,因为虚拟 DOM 是 React 内部在用,无需真实 DOM 上那么多的属性。
- 虚拟 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 表达式:
- 表达式:一个表达式会产生一个值,可以放在任何一个需要值的地方
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
状态constructor
、render
、自定义方法的调用次数
<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.propTypes
和 Person.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="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> </div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
回调形式的 ref
要点:
c => this.input1 = c
就是给组件实例添加input1
属性,值为节点(currentNode)- 由于是箭头函数,因此
this
是render
函数里的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="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> </div>)}}ReactDOM.render(<Demo />, document.getElementById('test'))
</script>
关于回调 ref
执行次数的问题,官网 (opens new window)描述:
TIP
如果
ref
回调函数是以内联函数的方式定义的,在更新过程中它会被执行两次,第一次传入参数null
,然后第二次会传入参数 DOM 元素。这是因为在每次渲染时会创建一个新的函数实例,所以 React 清空旧的ref
并且设置新的。通过将ref
的回调函数定义成class
的绑定函数的方式可以避免上述问题,但是大多数情况下它是无关紧要的。
即内联函数形式,在更新过程重新调用render中 ref
回调会被执行两次,第一次传入 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="点击按钮提示数据" /> <button onClick={this.showData}>点我提示左侧的数据</button> <input onBlur={this.showData2} ref={this.myRef2} type="text" placeholder="失去焦点提示数据" /> </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="失去焦点提示数据" /> </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>)}
}
对上述受控组件的代码进行优化,希望把 saveUsername
和 savePassword
合并为一个函数。
要点:
- 高阶函数:参数为函数或者返回一个函数的函数,如
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
(组件挂载完毕调用)
更新阶段
- 父组件重新
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):
- 废弃三个钩子:
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
。在新版本中这三个钩子需要加UNSAFE_
前缀才能使用,后续可能会废弃。 - 新增两个钩子(实际场景用得很少):
getDerivedStateFromProps
、getSnapshotBeforeUpdate
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>}
}
组件化开发思想
- 拆分组件、实现静态组件,注意:
className
、style
的写法 - 动态初始化列表,如何确定将数据放在哪个组件的
state
中?
- 某个组件使用:放在其自身的
state
中 - 某些组件使用:放在他们共同的父组件
state
中,即状态提升
- 关于父子之间通信:
父传子:直接通过
props
传递子传父:父组件通过
props
给子组件传递一个函数,子组件调用该函数//子组件 this.props.XXX(函数)//父组件 <子组件 xxx(函数名)={对应的处理函数}/>
- 结构命名
写成这种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('是否删除'))
- 注意
defaultChecked
和checked
的区别,类似的还有:defaultValue
和value
- 拿复选框的值需要掉event.target.checked
- 状态在哪里,操作状态的方法就在哪里
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)
下载安装 PubSubJS
:npm 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 掉用即可
- 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。
- ES6 知识点:解构赋值 + 重命名
let obj = { a: { b: 1 } }//传统解构赋值
const { a } = obj//连续解构赋值
const {a: { b },
} = obj//连续解构赋值 + 重命名
const {a: { b: value },
} = obj
- 消息订阅与发布机制
- 先订阅,再发布(隔空对话)
- 适用于任意组件间通信
- 要在
componentWillUnmount
钩子中取消订阅
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
或 组件
后端路由:
value
是function
,用于处理客户端的请求- 注册路由:
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
- 导航区a标签改为Link 标签
<Link to='/about'>about</Link>
- 展示区写
<Routes><Route path="/about" element={<About />} />
</Routes>
<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 地址上,因此都能保留参数。params
和search
参数都会变成字符串
第一种 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()
分发action
给Reducers
执行Reducers
接收previousState
、action
两个参数,对状态进行加工后返回新状态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 核心对象,内部维护着
state
和reducer
- 核心 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({})
间接调用 reducer
由store
自动触发首次调用,传递的preState
为undefined
,action
为{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()()
- 作用: 用于包装 UI 组件生成容器组件
- 使用connect(
mapDispatchToProps
,mapDispatchToProps
)(UI组件)注意点:
- 该方法默认传入
state
与dispatch
- 可以省略
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组件的标签属性
- mapDispatchToProps函数返回的是一个对象;
- 返回的对象中的key就作为传递给UI组件props的key,value就作为传递给UI组件props的value
- mapDispatchToProps
用于传递操作状态的方法
- 可以省略
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
文件夹新建 actions
和 reducers
文件夹分别用于存放每个组件对应的 action
和 reducer
。
添加案列
新建 Person
组件对应的 action
和 reducer
:
--------------------------------------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
可以模拟三个钩子:componentDidMount
、componentDidUpdate
、componentWillUnmount
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
改进:
- 只有组件的
state
或props
的数据发生改变时才重新渲染
方式:
- 手动重写
shouldComponentUpdate(nextProps, nextState)
的逻辑,只有数据发生改变才返回true
- 使用
PureComponent
,它重写了shouldComponentUpdate()
, 只有state
或props
数据有变化才返回true
TIP
- 它只是进行
state
和props
数据的浅比较, 如果只是数据对象内部数据变了, 返回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的方法及使用相关推荐
- 视频教程-React 全家桶从入门到实战到源码-其他
React 全家桶从入门到实战到源码 上市公司前端开发工程师,专注于 React 技术栈,对 React 全家桶从 react-router 路由到 Redux 状态管理工具再到 webpack 打包 ...
- react全家桶都有什么
react全家桶都有:1.react是核心:2.redux相当于数据库:3.React Router是专为React设计的路由解决方案:4.axios用于浏览器和Node js的http客户端:5.A ...
- react全家桶从0到1(react-router4、redux、redux-saga)
本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redux-saga的核心配置会加以讲解, ...
- React全家桶(技术栈) redux 代码
React全家桶(技术栈) redux 代码 压缩包下载 https://download.csdn.net/download/qq_42740465/87629665 README.md ## 1. ...
- react全家桶从0搭建一个完整的react项目(react-router4、redux、redux-saga)
react全家桶从0到1(最新) 本文从零开始,逐步讲解如何用react全家桶搭建一个完整的react项目.文中针对react.webpack.babel.react-route.redux.redu ...
- 小邵教你玩转Typescript、ts版React全家桶脚手架
前言:大家好,我叫邵威儒,大家都喜欢喊我小邵,学的金融专业却凭借兴趣爱好入了程序猿的坑,从大学买的第一本vb和自学vb,我就与编程结下不解之缘,随后自学易语言写游戏辅助.交易软件,至今进入了前端领域, ...
- 【学习笔记】React+React全家桶学习笔记
文章目录 1 为什么要使用React 2 React的定义 3 React的三大特性 4 React入门 4.1 hello_react 4.2 虚拟DOM的创建 4.3 JSX 4.4 模块与组件, ...
- 技术胖的2019新版React全家桶免费视频(84集)
技术胖 2019年09月18日 阅读 29883 关注 技术胖的2019新版React全家桶免费视频(84集) 一共84集,从5月4日开始录制,到9月18日完成,5个月时间.如果是一个专业讲师,这进度 ...
- react全家桶实战(千峰教育)
说明:本笔记为本人基于千锋教育2022版React全家桶教程_react零基础入门到项目实战完整版的学习笔记,知识点不清或不全,可以到视频教程中学习 文章目录 一.安装create-react-app ...
最新文章
- mysql 使用位运算
- 【TX2】TX2开发板系统默认串口有ttyS0(调试口)、ttyTHS1、ttyTHS2、ttyTHS3,通过修改设备树文件,可以新增三个串口
- 一步一步使用Ext JS MVC与Asp.Net MVC 3开发简单的CMS后台管理系统之登录窗口调试...
- win10下输入法突然变成繁体了怎么设置回来?(繁體)(快捷鍵:ctrl + shift +f)
- 洛谷 [SDOI2015]约数个数和 解题报告
- 用户暴增下的收入降低,AWS面临尴尬
- 吴恩达《机器学习》 --- 神经网络
- 快照(Snapshot)
- [转载] python全局变量的使用
- mysql_fetch_row()获取显示数据
- Spring Cloud(Greenwich版)-05-Spring Cloud集成Ribbon(客户端负载均衡组件)
- Ubuntu16.0.4 通过Docker安装酷Q
- 关于一些初级ACM竞赛题目的分析和题解(八)
- 百度api翻译html,帮助文档首页
- 大数据平台的SQL查询引擎有哪些—SparkSQL
- to_param()函数和parameterize()函数
- hp 交换机远程连接_Hp服务器 iLO3 使用方法
- datetime的时值
- 创建自己第一个安卓程序_从一天创建和发布我的第一个应用程序中学到的东西...
- 人脸检测论文:FaceBoxes: A CPU Real-time Face Detector with High Accuracy及其Pytorch实现