react入门笔记
react
React由Facebook(现在叫 meta) 的软件工程师Jordan Walke创建。2013年的时候在社区开源。那么react是什么呢?React是一个把数据渲染为HTML视图的开源JavaScript 库 [ 视图层框架 ] 。React为程序员提供了一种子组件不能直接影响外层组件的模型 [ 单向数据流 ],数据改变时会对HTML文档进行有效的更新。
特点:
- 声明式开发
- 组件化
- 跨平台
- 虚拟dom fiber
- jsx
- 单向数据流
create-react-app
react脚手架
、基于webpack 构建 react 工程化环境 简称 cra
npm i create-react-app -g
create-react-app 项目名 // 创建项目
jsx
xml in js
<student>
<name>小明</name>
<age>18</age>
<gender>男</gender>
</student>
react中的jsx, 可以在js中直接 写标签,react会自动转换成 虚拟dom对象结构
数据 驱动框架,一定都会设计 虚拟dom, 是虚拟dom 编写 过于繁琐,vue解决方案设计 sfc 在template标签直接 写 标签,自己自动编译成虚拟dom
react解决方案 在js代码直接 写标签, react会自动将 js标签 编译成虚拟dom
洗脑洗脑洗脑洗脑:
react中 在 js看到任意标签, 理解为这是对象对象对象对象
jsx基础语法
- 任意一个jsx 必须包裹在 闭合标签
- 想要在 jsx中 定义 js表达式 就要加 {} 包裹 标签值和属性
- 注释 {/* 这是jsx注释 */}
- jsx标签某些属性 如果 和 js关键字冲突, 定义其他语义 映射
比如 for --> htmlFor class --> className - 标签支持 批量 展开 一个对象 自动将对象每个属性转换成标签的属性
const obj = {
id: 'box',
className: 'wrap'
}
<div {...obj}>1111</div>
react组件
注意
所有组件 首字母必须大写
文件 后缀名建议 为 jsx
函数式组件
现在是 趋势
- 定义函数 返回jsx 注意 首字母必须大写 参数 函数是组件的 第一个参数 props接收 自定义属性
import React from "react";
// react函数式组件
const App = (props) => {
console.log(props);
return (
<div>
<h2>这是函数式组件</h2>
</div>
)
}
export default App
- 调用 以标签形式调用 通过 标签 自定义属性传递props
<App title="主标题" subTitle="副标题"/>
// 相当于调用函数
react vscode 插件
ES7+ React/Redux/React-Native snippets
重要作业 复习 class
定义属性 两种方式
定义方法两种方式
继承
静态属性静态方法
类组件
- 定义
import React, { Component } from 'react';
class App extends Component{
// render方法渲染 虚拟dom 方法 必须有返回值
render(){
console.log(this);
return (
<div>
<h2>这是react的class组件</h2>
{
this.props.title
}
<br />
{
this.props.subTitle
}
</div>
)
}
}
- 使用组件
<App title="主标题" subTitle="副标题"/>
// 相当于 new 类 实例调用render 传递 自定义属性 挂载实例的 props属性上
类组件和 函数式组件的不同点:
- 函数式组件 没有内部状态
- 函数式组件 没有生命周期
jsx原理
原理:
React 包有一个方法 createElement(类似于vue中的h渲染函数), react会自动分析 jsx 标签的结构,并自动 调用 React.createElement 方法 将 写jsx标签编译成 虚拟dom对象
React.createElement('div', {
id: 'box',
className: 'aaa'
},[
React.createElement('p', {className:'op'}, ['这是p']),
React.createElement('span', {className:'span'}, ['这是span']),
'这是文本内容'
])
react组件的样式
内联
- style 必须是对象
- 属性 就是 css样式属性, 注意 连接符 - 去-变驼峰
- 属性值 可以写表达式
- 可以省略px单位
<div style={
{
width: 100,
height: 100+200,
backgroundColor: 'red'
}
}></div>
引入 外部的 样式文件
-
引入外部的css
-
使用 css预处理器 sass
cra 内置 sass 环境- 安装sass
npm i sass -D
注意:
不管是 css还是外部 sass文件,都没有做任何 作用域限制,作用到全局所有的标签
选择器编写不要 污染
css module
CSS Modules 通过对 CSS 类名重命名,保证每个类名的唯一性,从而避免样式冲突的问题。也就是说:所有类名都具有“局部作用域”,只在当前组件内部生效。在 React 脚手架中:文件名、类名、hash(随机)三部分,只需要指定类名即可 BEM。
- 定义 css module 命名方式 文件名.module.css
/*app.module.css*/
.box{
width: 200px;
height: 200px;
background-color: #d4ac33;
}
.box2{
width: 200px;
height: 200px;
background-color: #2724d5;
}
- 引入styles
import styles from './app.module.css'
/*
{
box: '编译后类名',
box2: '编译后类名'
}
*/
<div className={styles.box}></div>
<div className={styles.box2}></div>
-
scss 使用 css module
- 定义 xxx.module.scss
.box{ width: 200px; height: 200px; background-color: #cb6d6d; .a{ width: 100px; height: 100px; background-color: #c6d0af; } }
- 引入样式
import styles from './xxx.module.scss' // 编译成 { box: '编译后类名', a: '编译后类名' } <div className={styles.box}> <div className={styles.a}></div> </div>
scss 嵌套规则 编译成平行
定义:global即可嵌套
.box2{ width: 200px; height: 200px; background-color: #cb6d6d; :global{ .a{ width: 100px; height: 100px; background-color: #c6d0af; .b{ color: red; } } } }
使用时
<div className={styles.box2}> <div className="a"> <div className="b">aaaa</div> </div> </div>
css in js
理念:万物皆组件
styled-components
原理:
通过样式组件 定义 样式和结构
- 安装
npm i styled-components -S
- 基础使用
import styled, {keyframes} from 'styled-components';
// 定义样式组件
/*
编译成一个组件 Box 内容就是 div标签,且具有如下的样式
*/
const Box = styled.div`
width: 200px;
height: 200px;
background: red;
`
// 内容和选择器的嵌套
const Box2 = styled.div`
width: 200px;
height: 200px;
background: red;
p{
color: blue;
}
span{
color: green;
&:hover{
color: yellow;
}
}
`
// 继承
const Box3 = styled(Box)`
border: 5px solid #11ee56;
`
// 传递props
const Box4 = styled.div`
width: 200px;
height: 200px;
background: ${props => props.bgc?props.bgc:'#dc6666' };
`;
// 定义关键帧
const move = keyframes`
0% {
transform: rotate(45deg);
}
100% {
transform: rotate(-45deg);
}
`
const Box5 = styled.div`
width: 10px;
height: 200px;
background: red;
margin: 50px auto;
transform-origin: center bottom;
animation: ${move} 200ms infinite alternate linear;
`
export {
Box,
Box2,
Box3,
Box4,
Box5
}
通过 prop-types 实现 react组件 props的类型校验
- 安装
npm i prop-types -S
- 使用
// 1 引入 PropTypes
import PropTypes from 'prop-types'
class Todo extends Component{
}
// 或者 函数式组件
const Todo = (props) => {
}
// 定义函数 静态属性
Todo.propTypes = {
// 一下是常用类型校验
a: PropTypes.array,
b: PropTypes.bigint,
c: PropTypes.bool,
d: PropTypes.func,
e: PropTypes.number,
f: PropTypes.object,
g: PropTypes.string,
h: PropTypes.symbol,
// 是react组件 传递 需要加标签 实例化后<Todo/> 使用时 {props.i}
i: PropTypes.element,
// 是react组件 传递 使用时 <props.j/>
j: PropTypes.elementType,
// 必须是 Message的实例
k: PropTypes.instanceOf(Message),
// 枚举 值 必须是给定 多个值中的一个
o: PropTypes.oneOf(['News', 'Photos']),
// 类型必须是 给定多个类型中的一个
p: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number,
PropTypes.instanceOf(Message)
]),
// 固定类型的数组 比如 是 number数组
q: PropTypes.arrayOf(PropTypes.number),
// 对象 且属性值必须是固定类型
r: PropTypes.objectOf(PropTypes.number),
// 描述的对象结构, 对象 如下属性必须符合要求(对于其他属性没有做要求)
s: PropTypes.shape({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 描述对象结构, 对象必须 只有 如下 属性且必须满足 类型要求
t: PropTypes.exact({
optionalProperty: PropTypes.string,
requiredProperty: PropTypes.number.isRequired
}),
// 链式 调用 要求类型 且必传
u: PropTypes.func.isRequired,
}
props的默认值
直接定义组件静态属性 defaultProps 中定义默认值即可
class Todo extends Component {}
const Todo = (props) => {}
Todo.defaultProps = {}
props.children
类似于vue slot 实现,标签在使用时 父组件传递一些视图 模板给子组件
- 父组件中 子组件标签 嵌套jsx 传递内容
<Todo>
<div>
<h2>哈哈哈</h2>
<h3>嘿嘿嘿</h3>
</div>
</Todo>
- 子组件中 props的children 获取传递的jsx
const Todo = (props) => {
return (
<div>
{props.children}
</div>
)
}
class组件
state
注意:函数式组件没有内部状态管理
state管理组件 内部状态
语法:
直接定义 实例的state属性,在属性管理数据即可
- 给实例定义 state属性
class Todo extends Component {
// 外部直接定义
state = {
msg: '这是实例自己的状态'
};
// 在constructor中定义
constructor(){
super();
this.state = {
msg: '这是实例自己的状态',
isBeauty: true
}
};
}
-
修改state
注意:
react 不是mvvm框架, 不能直接修改state, 对于state 父类原型 提供了 两个方法让视图刷新- 直接修改state 调用 forceUpdate 强烈不推荐
- 使用setState 修改状态
1 传对象
2 传函数,函数返回值中修改this.setState({ msg: '修改的值', isBeauty: !this.state.isBeauty })
this.setState((state, props) => { return { msg: '这是修改后的值', isBeauty: !state.isBeauty } })
问题?
setState修改数据后 会产生副作用(再次调用render,生成组件新的虚拟dom,比较新老dom更新真实dom), setState设计成了 异步的 提高代码执行效率数据修改后,无法 直接获取 修改后的数据和视图的
如何获取修改后最新的数据和dom react 给setState设计了回调, 在数据修改后,且视图更新完成后触发
this.setState({}, () => { // 在这里可以拿到修改后最新的数据和dom })
react事件绑定
react合成事件
on事件首字母大写
onClick onMouseover onMousedown
绑定合成事件语法
<button onClick={事件函数}>按钮</button>
注意:
事件函数不能加括号
class组件 绑定事件函数的四种方式
- 行内定义箭头函数
不建议: 造成业务嵌套 视图模板中
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
() => {
this.setState({
isBeauty: !this.state.isBeauty
})
}
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
}
- 方法定义 原型上, 行内通过bind 改变this指向
不推荐: render 会多次触发 造成多次bind
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
this.clickBtn.bind(this)
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn(){
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
- 方法定义原型上, 在constructor 给实例定义 同名方法, 值为 原型方法 bind返回的函数
较为推荐
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
this.clickBtn = this.clickBtn.bind(this);
};
render() {
return (
<div>
<button onClick={
this.clickBtn
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn(){
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
- 在实例上定义方法 使用箭头函数形式
class语法: 如果实例上 方法 通过箭头函数定义,class将强行将 方法this 指向实例
推荐写法
class Todo extends Component {
constructor(){
super();
this.state = {
isBeauty: true
};
};
render() {
return (
<div>
<button onClick={
this.clickBtn
}>按钮</button>
{this.state.isBeauty ? '美女啊': '你真善良'}
</div>
)
};
clickBtn = () => {
this.setState({
isBeauty: !this.state.isBeauty
})
}
}
事件对象
事件函数的第一个参数就是事件对象
e.stopPropagation() // 取消冒泡
e.preventDefault() // 阻止默认事件
e.target // 获取事件源
事件传参
行内定义箭头函数充当事件 函数, 方法在 箭头函数中调用即可传参
class Todo extends Component {
render() {
return (
<div>
<button onClick={
(e) => {
this.clickBtn(5, e)
}
}>按钮</button>
</div>
)
};
clickBtn = (n, e) => {
alert(n)
e.target.style.background = 'red';
}
}
react数据渲染
- react条件渲染
使用短路或者三目表达式 实现 条件渲染
class Todo extends Component {
state = {
isShow: true
}
render() {
return (
<div>
<button onClick={
() => {
this.setState({
isShow: !this.state.isShow
})
}
}>{this.state.isShow? '隐藏': '显示'}</button>
{
this.state.isShow
&&
<div className="box"></div>
}
{
this.state.isShow
?
<div className="box"/>
:
<div className="box2"/>
}
</div>
)
}
}
- 循环渲染
使用 数组的map方法
class Todo extends Component {
state = {
arr: ['a', 'b', 'c', 'd']
}
render() {
return (
<div>
<ul>
{
this.state.arr.map((item, index) => {
return (
<li key={index}>
{item}
{index}
</li>
)
})
}
</ul>
</div>
)
}
}
- 渲染富文本
<div dangerouslySetInnerHTML={{__html: this.state.content}}></div>
- 容器组件
问题?
jsx语法要求,任意一个jsx 必须 包裹在一个闭合标签, 往往会造成, dom树 层级 过深, react提供容器组件 Fragment 作为 jsx容器 优点, 渲染时不会渲染任何标签
import React, { Fragment } from 'react'
import Todo from './Todo'
export default function App() {
return (
<Fragment>
<Todo />
</Fragment>
)
}
也可以使用 空标签充当容器
class Todo extends Component {
render() {
return (
<>
<h2>这是todo</h2>
</>
)
}
}
表单值得绑定
-
绑定初始值 (表单控件 初始值 等于 某个 状态,值是可变,变化后不会 改变状态)
提供了两个属性分别是
defaultValue
defaultCheckedclass Todo extends Component { state = { msg: '这是初始值', isBeauty: true } render() { return ( <> <input type="text" defaultValue={this.state.msg}/> <br /> {this.state.msg} <hr /> <input type="checkbox" defaultChecked={this.state.isBeauty} /> <br /> { this.state.isBeauty?'真的':'假的' } </> ) } }
-
双向绑定
原理:
直接将状态绑定 表单控件的 value和checked属性, 添加onChange 事件 在事件函数
e.target.value e.target.checked 赋值给 绑定state即可class Todo extends Component { state = { msg: '这是初始值', isBeauty: true } render() { return ( <> <input type="text" value={this.state.msg} onChange={ this.handleInput }/> <br /> {this.state.msg} <hr /> <input type="checkbox" checked={this.state.isBeauty} onChange={ this.handleChecked }/> <br /> { this.state.isBeauty ? '美女你好' : '姑娘你好' } </> ) }; handleInput =(e) => { console.log(e.target.value); this.setState({ msg: e.target.value }) }; handleChecked = (e) => { this.setState({ isBeauty: e.target.checked }) } }
react组件通信方案
-
父向子 props传递参数
-
子向父通信
原理:
父组件中定义 方法, 通过props 传递给子组件这个方法,子组件 调用 该方法 通过方法参数 可以 传递 子组件数据- 父组件
class App extends Component { state = { msg: '' } render() { return ( <div> <h2>父组件</h2> {this.state.msg} <hr /> <Todo fn={this.fn}/> {/*通过props将父组件方法 传递给子组件*/} </div> ) }; fn = (msg) => { // 父组件方法 alert('我调用了'+ msg) this.setState({ msg }) } }
- 子组件中调用 父组件 通过props 传递方法
class Todo extends Component { state = { msg: '这是子组件的数据' } render() { return ( <div> <button onClick={ () => { this.props.fn(this.state.msg) {/*子组件调用 父组件传递的方法*/} } }>子向父通信</button> <h2>子组件</h2> </div> ) } }
-
兄弟组件通信
可以利用PubSub 发布订阅的 js库进行兄弟组件通信
npm i PubSub
// 创建实例
const pubsub = new PubSub();
// 兄弟组件1 中 订阅一个消息
pubsub.subscribe('biubiu', (data) => {
})
// 兄弟组件2 中发布该消息
pubsub.publish('biubiu', 携带的数据)
createRef class组件中 定义 ref 转发 dom或子组件实例
import React, { Component, createRef } from 'react'
import Todo from './Todo'
export default class App extends Component {
constructor(){
super();
// 在实例上存储 容器
this.btnRef = createRef(null);
this.todoRef = createRef(null);
};
render() {
return (
<div>
{/* 挂载容器 将子组件实例dom存储到容器中*/}
<Todo ref={this.todoRef}/>
<button ref={this.btnRef}>按钮</button>
</div>
)
};
componentDidMount(){
// 在容器current属性中获取
console.log(this.btnRef.current);
console.log(this.todoRef.current);
};
}
五一作业:
回来收两个项目 pc和移动 录制 视频 等
提前学习react后面视频 学习到 react-router之前
class组件 生命周期
初始化
- constructor
初始化 创建实例 在这里可以 定义state 初始 给实例挂载 - static getDerivedStateFromProps
- render
生成 组件的虚拟dom视图结构 - componentDidMount
真实dom构建完成, 1 组件初始化 数据函数的请求调用 2 任何初始化获取dom操作
更新阶段
- static getDerivedStateFromProps
- shouldComponentUpdate
- render
- componentDidUpdate
真实dom更新完成,在这里可以获取最新的dom 不建议使用
componentWillUnmount
组件即将卸载
使用场景:
注销一些全局挂载,比如 定时器、 全局事件等
static getDerivedStateFromProps
类似于vue计算属性, 从组件已有的props和state中 派生出新的状态
react 父子组件 更新机制
react中 只要祖先组件更新, 后代默认一定会更新,不管导致 祖先组件更新数据 有没有在 后代组件中使用
问题?
后代组件 无意义的 re-render
如何解决 react 后代组件 因为 祖先组件的 更新 导致 无意义 re-render
- 使用 生命周期钩子 shouldComopnentUpdate (开发不用、面试要用)
class TodoItem extends Component {
shouldComponentUpdate(nextProps, nextState){
/*
函数 在每一次 子组件 更新render前触发, 拦截后代组件更新, return false 子组件 永远不更新
return true 子组件更新(只要祖先组件更新)
参数1
nextProps 如果更新,更新后 最新的props this.props更新的前组件props
nextState 更新最新的state this.state 更新的前state
*/
// console.log(nextProps.item, this.props.item);
return nextProps.item !== this.props.item
};
render() {
console.log('子组件render');
return (
<div>
<h2>{this.props.item}</h2>
<button onClick={
() => {
this.props.changeMsg(this.props.index)
}
}>改变值</button>
</div>
)
}
}
原理:
在每一次子组件更新前,比较新老props和新老state,如果有改变 则 shouldComponentUpdate return true让子组件更新,否则不更新
- 使用 PureComponent
子组件 不再继承 Component 使用 PureComponent
原理:
react 自动在 子组件 更新前 ,对于 所有 新老 props和state 进行浅层比较, 有改变 子组件更新没有改变子组件不更新
函数式组件
react函数式组件 问题?
比如 没有 内部状态 没有生命周期钩子
react在 16.8 推出了 react hook函数 解决函数式组件 问题
1 hook函数常见命名是 useXxx (use功能名)
2 hook函数 只能在 函数式组件 内部使用,其他函数 或者 外部无法使用的
useState
解决函数式组件中 没有内部状态的问题
function App() {
/*
useState传入初始值 返回值 是数组
1 个 当前值
2 个 修改值得方法, 方法要求 必须传入一个新值 传入新值时 数据修改视图自动刷新
主要针对引用类型, 一定要克隆传入
*/
const [num, setNum] = useState(10);
const [arr, setArr] = useState([1,2,3,4]);
return (
<div>
<button onClick={
()=> {
setNum(num+1)
}
}>+</button>
{
num
}
<hr />
<button onClick = {
() => {
setArr([
...arr,
arr.length+1
])
}
}>增加li </button>
<ul>
{arr.map(el => (
<li key={el}>
{el}
</li>
))}
</ul>
</div>
)
}
注意:
useState返回set函数 修改值 一定要传入新值 主要针对引用类型,一定要 克隆后传入新值
useState set函数修改数据,是异步的,且不支持 异步回调
useEffect
- 解决react 函数式组件 没有生命周期钩子函数的问题 (模拟 生命周期 钩子函数)
- 监听数据变化, 解决 useState 异步问题
1 默认 会在初始化完成和更新完成都触发 相当于 componentDidMount和 componentDidUpdate
useEffect(() => {
// 相当于 componentDidMount和 componentDidUpdate
})
注意: 这种不要用,经常会造成死循环
2 useEffect 定义第二个参数 是数组 数组中定义 更新阶段触发的 依赖
更新阶段只有依赖中 任意一个发生改变时 才触发
功能:
类似 vue中watch(立即触发的watch)
解决 useState set函数修改数据 异步的问题
useEffect(() => {}, [a,b,c])
3 模拟 初始化完成的 生命周期钩子函数
作用: 类似 componentDidMount vue 的 onMounted
组件初始化 请求函数的调用
第三方插件的初始化
// 定义空的依赖即可
useEffect(() => {}, [])
4 模拟组件 卸载前的生命周期钩子函数
useEffect(() => {
return () => {
// 返回的函数 会在卸载前触发
}
}, [])
context
react 提供了 可以 实现 跨层级 组件 数据传递的方案
- 创建context对象
import { createContext } from "react";
// 创建context对象
const context = createContext();
/*
context对象下 有两个属性 都是react组件
1 Provider 数据提供者, 通过 value属性挂载公共的数据 只能有 Provider的后代组件 通过Consumer获取
2 Consumer
后台组件用于 获取Provider提供的value的
*/
const { Provider, Consumer } = context;
export {
Provider,
Consumer
}
- Provider 包裹app 并通过value 提供数据
const data = {
a: 10,
b: 20
}
root.render(
<Provider value={data}>
<App />
</Provider>
);
- 需要获取 数据的 后台组件引入 Consumer 获取数据(只针对class组件)
class A extends Component {
render() {
return (
<Consumer>
{
(data) => (
<div>
<h2>a组件</h2>
{data.a}
</div>
)
}
</Consumer>
)
}
}
高阶组件 HOC (hign order component)
面试题?
fn(5)(6) // 30
function fn(a){
return function(b){
return a*b
}
}
高阶组件 本质上就是 高阶函数(特殊高阶),用来抽象或者 修饰 普通组件,可以给普通组件 添加额外视图,或者额外props等
实现:
特殊高阶函数, 接收参数就是被修饰的组件, 返回了一个组件
注意:
withXxx
问题?
高阶组件 本质上劫持普通组件 到内部使用, 返回新的组件, 造成 普通组件的props丢失
需要在 高阶组件内部 再一次 传递 props给被修饰的组件
import React, { Fragment } from 'react';
const withTpl = (DecoratorComponent) => {
return (props) => {
console.log(props, '222');
return (
<Fragment>
<h1>这是头部</h1>
<DecoratorComponent {...props} msg="这是高阶组件送的props"/>
<h1>这是尾部</h1>
</Fragment>
)
}
}
使用
export default withTpl(被修饰的组件)
useRef
函数式组件中用于获取 dom 或者 class子组件实例
function Todo() {
// 创建容器
const btnRef = useRef();
const childRef = useRef();
const child2Ref = useRef();
useEffect(() => {
console.log(btnRef.current);
console.log(childRef.current);
}, [])
return (
<div>
<button ref={btnRef}>按钮</button>
<CommonHead ref={childRef}/>
</div>
)
}
问题?
函数式 组件 无法 通过ref 绑定到容器上
- 函数式子组件 可以通过 forwardRef 将 子组件标签 绑定ref 容器 传递到子组件内部
<CommonHead2 ref={child2Ref}/>
// 这是函数式子组件, 绑定容器可以通过 forwardRef 传递到子组件内部
- 子组件内部 forwardRef 获取 容器 将内部 dom 挂载到容器上
forwardRef(function CommonHead2(props,child2Ref) {
return (
<div>
<h2 ref={child2Ref}>这是子组件2</h2>
</div>
)
})
useMemo
将一个函数计算返回值 缓存起来,并返回, 指定依赖, 在 函数式组件多次 更新,依赖没有改变,使用缓存的值
功能类似于vue的计算属性
const sum = useMemo(() => {
return num1+num2
}, [num1, num2])
注意:
1 useMemo回调一定要有返回值,
2 useMemo手动指定依赖, 在函数式组件重新调用时,依赖改变 才重新计算
useCallback
是useMemo语法糖
useMemo不同点在于:
1 useMemo 回调一定要有返回值, useCallback不需要
2 useMemo调用后返回的 是 回调 return 的值 useCallback直接返回 callback函数
3 都可以指定依赖, 依赖改变,useCallback 重新调用,重新返回新的callback,否则不调用使用上一次返回的callback函数
function Todo() {
const [num2, setNum2] = useState(20);
const [num3, setNum3] = useState(30);
// 当Todo刷新重新调用,只有num2改变 addNum2才会得到一个新的函数, 使用上一次缓存的函数
const addNum2 = useCallback(() => {
console.log(1);
setNum2(num2+1)
}, [num2])
return (
<div>
<button
onClick={addNum2}
>
num2+
</button>
{num2}
<br />
<hr />
<button
onClick={() => {
setNum3(num3 + 1);
}}
>
num3+
</button>
{num3}
</div>
);
}
useContext
函数式组件中 获取 context对象 Provider提供的value
const data = useContext(context); // 返回该 context Provider提供的value
react-router
特点:
万物皆组件
路由实现也是通过组件定义路由
提供了三个包
react-router 核心包 (包含了 react-router-dom和react-router-native)
react-router-dom 专门用于 b/s应用
react-router-native 专门用于 c/s 移动端app
基础使用
- 安装
npm i react-router-dom -S
路由根组件
包裹 App组件 路由才可以生效, 不同 根组件决定路由不同模式
HashRouter hash模式路由
BrowserRouter history模式路由
import { HashRouter } from 'react-router-dom'
<HashRouter>
<App/>
</HashRouter>
import { BrowserRouter } from 'react-router-dom'
<BrowserRouter>
<App/>
</BrowserRouter>
定义路由组件
注意:
路由定义组件 即是路由定义 也是路由出口
import { Routes, Route } from 'react-router-dom'
<Routes>
<Route path="/" element={<Home/>}/>
<Route path="/about" element={<About/>}/>
<Route path="/news" element={<News/>}/>
</Routes>
导航组件
-
Link
属性如下
to 控制路由跳转path 可以是 字符串(直接写路由地址) 对象 {pathname: ‘/xxx’}
replace boolean 跳转时 覆盖当前历史记录
state 对象 (传参)
注意:
Link只是单纯导航,对于匹配路由 没有做高亮样式的处理 -
NavLink
具有Link所有的属性,同时 增加了 匹配导航 高亮样式处理- 默认对于匹配路由添加 active类
- 函数 自定义 className
<NavLink to="/news" className={({isActive}) => isActive?'active':'inactive'}>新闻页</NavLink>
- 内联自定义高亮样式
<NavLink to="/news" style={ ({isActive}) => isActive? {color:'red'}: {color: 'gray'} }>新闻页</NavLink>
- 嵌套children
<NavLink to="/about" replace> { ({isActive}) => ( <button className={isActive?'aaa':'bbb'}>关于我们</button> ) } </NavLink>
重定向组件
Navigate
注意:
Navigate 一定要有条件的渲染, 否则会造成路由死循环
to控制重定向的 地址
replace 默认为true
<Route path="/" element={<Navigate to="/home"/>}/>
利用表达式
{
!isLogin()
&&
<Navigate to="/login"/>
}
404问题
react-router 提供了特殊的path * 匹配任意路由地址 且优先级别最低
<Route path="*" element={<NotFound/>}/>
嵌套路由
- 父级路由 Route 嵌套 子级路由规则
子路由 path 可以省略 父path和 / 会自动补全
<Route path="/news" element={<News />}>
<Route path="/news/native" element={<NativeNews />} />
<Route path="abroad" element={<AbroadNews />} />
</Route>
- 父级路由组件中定义 Outlet组件作为出口
function News() {
return (
<div>
<h3>这是新闻页</h3>
<Link to="/news/native">国内新闻</Link>
<Link to="abroad">国外新闻</Link>
<Outlet/>
</div>
)
}
编程式导航
- useLocation
每一次调用 返回新的路由静态参数信息 (类比 vue中的 useRoute)
const location = useLoaction()
结合useEffect路由监听
useEffect(() => {
console.log(location, 222);
}, [location])
注意:
默认情况下 App.jsx 根组件 在路由切换时 不会重新触发了
想要重新触发,监听 location即可
原因:
useLocation 每一次需要返回新的对象,必须重新执行 useLocation,让每一次路由切换时 App.jsx重新调用 再一次调用useLocation 返回新的location
- useNavigate
得到编程式导航的api
路由跳转
历史记录操作等
const navigate = useNavigate();
// 普通跳转
navigate('/home')
// replace跳转
navigate('/home', {
replace: true
})
// 传递state 参数
navigate('/home', {
state: {
a: 10,
b: 20
}
})
路由跳转传参
-
动态路由传参
- 定义动态参数
<Route path="/news/:id" element={<News />} />
- 跳转时 按照 path 顺序 给 动态参数赋值
navigate('/news/5')
- 获取 使用 useParams hook函数
const params = useParams();// 解析好的 动态参数 // params {id: 5}
-
state传参
- 传参
<Link to="/news" state={{a: 10,b: 20}}>到新闻</Link> navigate('/news', {state: {a: 10,b: 20}})
- 获取 使用 useLocation hook获取
const location = useLoaction(); // location.state.参数名
注意:
隐式 传参
刷新不丢失 -
search传参
- path后面携带 search参数
navigate('/news?a=10&b=20')
- useSearchParams 获取参数
const [searchParams, setSearchParams] = useSearchParams(); /* searchParams 对象 获取 search获取 searchParams.get('a') // 10 searchParams.get('b') // 20 setSearchParams 函数 动态设置search参数 setSearchParams('c=1000') 动态设置当前 url search参数的值 */
路由登录鉴权
- 直接在组件中判断
{
!isLogin()
&&
<Navigate to="/login"/>
}
const navigate= useNavigate();
useEffect(() => {
if (!isLogin()) {
navigate('/login')
}
}, [])
- 在路由中判断
<Route path="/about" element={
isLogin()? <About/> : <Navigate to="/login" />
} />
改变 组件式路由为 config路由
路由懒加载
使用 React.lazy结合 Suspense 组件 实现
引入组件使用 lazy方法
import { lazy } from "react";
const About = lazy(() => import("../pages/About"))
使用 Suspense 组件 包裹 异步引入的组件
<Suspense fallback={
<div>
加载中...
</div>
}>
渲染异步的组件
</Suspense>
React.memo 解决函数式组件 性能问题
高阶组件 解决 react 函数式组件 性能问题?
react 祖先组件更新 后代组件一定会更新,不管 导致祖先组件更新数据 有无在后代组件中使用
class组件可以通过 shouldComponentUpdate和 PureComponent解决?
函数式组件怎么解决?
函数式组件 更新 会让函数重新调用
React.memo 这是高阶组件,功能 在祖先组件 刷新重新调用 后代组件 只有当他的props中任意一个改变 后代组件才会重新触发 刷新视图
import React, {memo} from 'react'
const Todo = (props) => {
return (
<div></div>
)
}
export default memo(Todo)
redux
用的更多 基于 redux封装的库 比如 @reduxjs/toolkit dva mobx
js状态管理的库,
核心概念:
- state 必须是只读的
- 单一数据源
- reducer 是纯函数
函数的返回值 取决于 参数,且中间没有任何副作用
安装
npm i redux -S
仓库创建
import { legacy_createStore as createStore } from 'redux';
import { cloneDeep } from 'lodash'
const defaultState = {
num: 10
}
/*
reducer必须是纯函数
接收两个参数
state修改前 store中的state
参数2 组件 dispatch action
*/
const reducer = (state = defaultState, action ) => {
// state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新 必须深拷贝
const newState = cloneDeep(state);
return newState;
}
// 参数1 传入 仓库reducer
const store = createStore(reducer);
export default store
组件中使用 store
- 获取state
store.getState() // 获取仓库中的state
// 一般建议 将 获取的state 结合 useState存储, 当数据发生改变, 使用useState提供set函数 重新 store.getState() 重新赋值 让视图刷新
const [state, setState] = useState(store.getState());
- 提交 action 修改state
// 触发一个行为,告诉仓库我们要做什么
/*
action是一个对象
必须有一个属性
type 做什么
*/
store.dispatch({
type: 'ADD_NUM',
data: 5
})
- reducer 判断 action的type 属性 对于state做修改
const reducer = (state = defaultState, action ) => {
// state是只读的 reducer 必须返回 新的state 仓库才能存储和刷新 必须深拷贝
const newState = cloneDeep(state);
// 判断action type 修改state
switch (action.type) {
case 'ADD_NUM':
newState.num += action.data
break;
default:
break;
}
return newState;
}
- 组件中 通过 store.subscribe方法 订阅 state 变化 获取的store最新的值
// 订阅仓库 state 改变
store.subscribe(() => {
setState(store.getState())
})
单独提取actionCreator 和 actionType
- action的type 是直接写的字符串 (不管在dispatch action 还是 在reducer中的判断)
当 字符串 某个字符写错了 会造成数据不改变 且视图不做任何刷新 (无法调试代码)
使用常量 保存type 字符串 - dispatch action时, 直接写的对象, action 没有维护性 和 复用性
redux 处理异步
redux 通过 异步 redux 插件来完成
- redux-thunk
- redux-saga (基于es6 generator)
以redux-thunk 举例
1 安装
npm i redux-thunk -S
2 store 使用插件
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer';
// 参数1 传入 仓库reducer
const store = createStore(reducer, applyMiddleware(reducer));
3 如何定义异步请求
改写 actionCreator
变成 函数 返回 函数
const fetch_items = (params = {}) => {
return dispatch => {
axios.get('xxx', {params}).then(res => {
if (res.data.code === 200) {
dispatch({
type: 'xxx',
data: res.data.data
})
}
})
}
}
4 组件中 dispatch 异步 actionCreator
store.dispatch(fetch_items(page: 1, pageSize: 10))
lodash
react-redux 连接redux仓库和react组件
- 安装
npm i react-redux -S
- 入口函数中 引入 Provider 和store Provider 将store 挂载
import { Provider } from 'react-redux';
import store from './store';
<Provider store={store}>
<App />
</Provider>
- 组件中引入 useSelector 和 useDispatch 获取state 和提交action
import { useSelector, useDispatch } from 'react-redux'
const num = useSelector(state => state.num);
const cates = useSelector(state => state.cates);
dispatch(fetch_cates())
dispatch(add_num(10))
scribe(() => {
setState(store.getState())
})
单独提取actionCreator 和 actionType
- action的type 是直接写的字符串 (不管在dispatch action 还是 在reducer中的判断)
当 字符串 某个字符写错了 会造成数据不改变 且视图不做任何刷新 (无法调试代码)
使用常量 保存type 字符串 - dispatch action时, 直接写的对象, action 没有维护性 和 复用性
redux 处理异步
redux 通过 异步 redux 插件来完成
- redux-thunk
- redux-saga (基于es6 generator)
以redux-thunk 举例
1 安装
npm i redux-thunk -S
2 store 使用插件
import { legacy_createStore as createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
import reducer from './reducer';
// 参数1 传入 仓库reducer
const store = createStore(reducer, applyMiddleware(reducer));
3 如何定义异步请求
改写 actionCreator
变成 函数 返回 函数
const fetch_items = (params = {}) => {
return dispatch => {
axios.get('xxx', {params}).then(res => {
if (res.data.code === 200) {
dispatch({
type: 'xxx',
data: res.data.data
})
}
})
}
}
4 组件中 dispatch 异步 actionCreator
store.dispatch(fetch_items(page: 1, pageSize: 10))
lodash
react-redux 连接redux仓库和react组件
- 安装
npm i react-redux -S
- 入口函数中 引入 Provider 和store Provider 将store 挂载
import { Provider } from 'react-redux';
import store from './store';
<Provider store={store}>
<App />
</Provider>
- 组件中引入 useSelector 和 useDispatch 获取state 和提交action
import { useSelector, useDispatch } from 'react-redux'
const num = useSelector(state => state.num);
const cates = useSelector(state => state.cates);
dispatch(fetch_cates())
dispatch(add_num(10))
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。 如若内容造成侵权/违法违规/事实不符,请联系我的编程经验分享网邮箱:veading@qq.com进行投诉反馈,一经查实,立即删除!