1. react 创建项目
npm install -g create-react-app //安装脚手架
create-react-app myapp // 创建 react 项目
npm start // 启动项目
2. react 新建一个项目
- 可以删除 src 目录下的所有文件,
- 然后创建一个 index.js 文件
index.js文件中的内容
// react 可以识别并解析 jsx 语法
import React from "react";
// react-dom 可以渲染dom树,然后插入到页面中指定的元素中。
import ReactDOM from 'react-dom'
// react-dom 的render 方法可以渲染 dom 元素 ,等价于下面的 create 写法
ReactDOM.render(<div>111</div>,document.getElementById('root'))
// ReactDOM.render(React.createElement('div',{id:'aaa',class:'bbb'},'1111'),document.getElementById('root'))
3. react 类组件
- react 中创建类组件,需要从 react.component 中继承。
记得要引入 react
- 创建完类组件以后需要,将创建的类组件
export default
暴露出去 (注意:vue中不需要暴露
)- 在需要的组件中 引入上面暴露的类组件
注意:在引入class 类组件的时候,组件名字首字母要大写
react 组件
化与vue组件
化的区别。
- vue 组件
不需要 export default
暴露,但是引入组件的时候,需要在 components 中注册引入的组件- react 类组件
需要 export default
暴露, 但是引入组件的时候,不需要注册 ,可以直接写一个 单标签或者双标签使用即可。
// 创建一个 react 类组件
import React from 'react'
export default class App extends React.Component {
render() {
return <div>hello,coco 现在测试 react类组件</div>
}
}
//使用
import App from './01-base/01-class类组件'
ReactDOM.render(<App></App>, document.getElementById('root'))
4. react 函数式组件
- 可以直接写一个函数,在函数中 return jsx 语法
注意:
- 16.8 之前 ,函数时组件也叫
无状态组件
- 16.8 之后,可以使用 react hooks 来改动 状态。
export default function App (){
return (
<div>
hello coco,现在测试函数式组件!!!
</div>
)
}
// 也可以写成 声明变量和箭头函数的方式来创建 函数式组件
const Footer = () => <div>footer组件</div>
// 使用
import App from './01-base/02-fn函数式组件'
ReactDOM.render(<App></App>, document.getElementById('root'))
5. react 中编写css 样式
的两种方式
- 行内样式
- 外部引入 css 样式,使用 className 来编写 css样式。
- 注意:写行内样式时,要在 jsx语法(花括号)中 进行编写。
import React, { Component } from 'react'
import './css/index.css'
export default class Style extends Component {
render() {
var styleObj = {
background: 'tomato',
}
return (
<div>
<div style={{ background: 'red' }}>行内样式1</div>
<div style={styleObj}>行内样式2</div>
{/* 注意:react中 起class 类名需要使用 className ,使用 class 的话会被误认为 js中的关键字 */}
<div className="componentStyle">行内样式2</div>
{/* 同样的 for 在react jsx的语法中也属于是 关键字,所以下面这种情景需要 使用 htmlFor来规避这种问题 */}
<label htmlFor="username">用户:</label>
<input type="text" id="username"></input>
</div>
)
}
}
6. react 事件绑定
- 函数声明式 , 在 jsx 语法中调用时不用带 小括号 ()
- 在匿名函数中调用 函数声明式,需要带小括号 ()
推荐
- 四种事件绑定中,
方法二需要重新绑定 this的指向
。- 其他三种方法因为有箭头函数,所以this指向与 外面(render)保持一致。所以不需要重新绑定this的指向。
- 推荐 使用
第四种
事件绑定的方法,因为传递参数的时候比较方便
{/* 事件绑定1:箭头函数(匿名函数) */}
<button
onClick={() => {
console.log('click1',this.a)
}}
>
add1
</button>
{/* 事件绑定2:函数声明式*/}
<button onClick={this.handleClick2.bind(this)}>add2</button>
{/* 事件绑定3:函数声明式*/}
<button onClick={this.handleClick3}>add3</button>
{/* 事件绑定4:箭头函数(匿名函数) + 函数声明式*/}
<button onClick={()=>{this.handleClick4()}}>add4</button>
handleClick2() {
console.log('click2',this.a)
}
handleClick3 = () => {
console.log('click3',this.a)
}
handleClick4 () {
console.log('click4',this.a)
}
7. react ref 引用
方法一:直接使用 ref定义一个字符串,然后使用 this.refs.字符串 获取绑定的dom元素
方法二:使用 React.createRef ,创建一个变量,使用 this.变量.current 来获取dom元素
mytext2 = React.createRef()
render() {
return (
<div>
{/* 方法一:使用ref */}
<input ref="mytext"></input>
<button
onClick={() => {
// 获取 dom 元素
console.log(this.refs.mytext.value);
}}
>
获取input的值
</button>
{/* 方法二:使用 React.createRef */}
<input ref={this.mytext2}></input>
<button
onClick={() => {
// 获取 dom 元素
console.log(this.mytext2.current.value);
}}
>
获取input的值
</button>
</div>
)
8. react 条件渲染
1. 使用与,或运算符 进行条件渲染
{this.state.active === 1 && <Buy></Buy>}
2. 使用三目运算符 进行条件渲染
{this.state.active === 1 ? <Buy></Buy> : null}
9. react 组件之间属性的传递
类组件传参
- 在组件上定义
属性名
,形式变量={ '内容' }
- 在子组件的 render 函数中通过
this.props.变量
来获取从父组件传递属性.
父组件:
<Navbar title="首页" leftShow={false}></Navbar>
对象传参:
var obj = {
title: '测试',
leftShow: false,
}
<Navbar {...obj}></Navbar>
Navbar组件:
import React, { Component } from 'react'
export default class index extends Component {
render() {
console.log(this.props,'父组件传递的属性')
let { title, leftShow } = this.props
return (
<div>
{leftShow && <button>返回</button>}
navbar--{title}
{leftShow && <button>首页</button>}
</div>
)
}
}
函数式组件传参
- 传参方式和类组件的传参方式一样。
- 函数式组件获取参数的方式: 给函数式组件传入一个 props 参数,然后通过 props.属性来获取父组件传来的属性
import React from 'react'
1. 给函数式组件传入一个参数,用来接收父组件传递的属性。
export default function Sidebar(props) {
2. 将父组件传来的属性进行结构赋值,也可以直接使用 props.属性来获取。
let { content } = props
return <div>{content}</div>
}
10. react dangerouslySetInnerHTML
- 相当于 vue的 v-html
- 用法:可以解析 html 标签
<span dangerouslySetInnerHTML={{ __html: item.value }}></span>
11. react 请求数据
- 在没有学生命周期的时候,建议将请求放在
constructor
函数中。(注意:constructor函数中必须有 super()函数 )- 请求数据最好在
生命周期函数
中进行。
12. react setState 更新状态
注意事项:
- setState 处于
同步逻辑
中,异步更新状态和dom
,- setState 处于
异步逻辑
中,同步更新状态和dom
,- setState 第二个参数是一个回调函数, 在setState 更新完状态和dom后触发。
state = {
count: 1,
}
render() {
return (
<div>
{this.state.count}
<button onClick={this.handle1}>add1</button>
<button onClick={this.handle2}>add2</button>
</div>
)
}
handle1 = () => {
// 此时是同步逻辑,所以 setState 异步更新状态
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, 'setState第二个参数') //2
}
)
console.log(this.state.count) //1
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, 'setState第二个参数') //2
}
)
console.log(this.state.count) //1
this.setState(
{
count: this.state.count + 1,
},
() => {
console.log(this.state.count, 'setState第二个参数') //2
}
)
console.log(this.state.count) //1
}
handle2 = () => {
setTimeout(() => {
// 此时 setState 处于异步逻辑,所以 setState 同步更新状态和 dom
this.setState({
count: this.state.count + 1,
})
console.log(this.state.count) //2
this.setState({
count: this.state.count + 1,
})
console.log(this.state.count) //3
this.setState({
count: this.state.count + 1,
})
console.log(this.state.count) //4
}, 0)
}
13. react 属性验证 prop-types
- 在需要使用属性验证的组件 引入
prop-types
- 在组件中定义一个类属性
static propTypes = { }
- 注意:
上面定义的类属性名是固定的.
- 在上面定义的类属性中 以
key:value
的形式,进行属性验证
设置默认属性
- 在定义的class类中,通过
static defaultProps = { }
来设定从父组件传递属性的默认值
import React, { Component } from 'react'
// 1. 引入 prop-types 模块
import CocoProptypes from 'prop-types'
export default class index extends Component {
// 2. 定义 propTypes 类属性,用来对父组件传递过来的属性进行验证.
static propTypes = {
title: CocoProptypes.string,
leftShow: CocoProptypes.bool,
}
// 3. 设置默认属性
static defaultProps = {
leftShow: true,
}
// 可以查看 属性验证规则
console.log(CocoProptypes)
render() {
let { title, leftShow } = this.props
return (
<div>
{leftShow && <button>返回</button>}
navbar--{title}
{leftShow && <button>首页</button>}
</div>
)
}
}
类属性 和 对象属性
类属性
通过定义的类直接打点添加一个属性
,也可以在 定义的class中使用 static 关键字来定义 类属性.
对象属性
在定义的class类中,不添加关键字定义的属性是对象属性
类属性 和 对象属性的读取方式
- 类属性,可以通过 定义的class类 直接打点调用(读取)
- 对象属性,首先要 new class() ,示例一个定义的class类,然后打点调用(读取)
class test {
// 对象属性
a = 100
// 类属性
static b = 88
}
// 类属性
test.a = 66
console.log(test.a, test.b, new test().a) // 66 88 100
14. react 父子组件通信
- 子组件要想改变父组件传过来的状态,可以触发父组件定义的事件来改变父组件的状态。
import React, { Component } from 'react'
class Navbar extends Component {
render() {
return (
<div style={{ background: 'red' }}>
<button
onClick={() => {
// 触发父组件定义的事件
this.props.hideSidebar()
}}
>
click
</button>
</div>
)
}
}
class Sidebar extends Component {
render() {
return (
<div style={{ width: '30%', background: 'yellow' }}>
<ul>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
<li>111111111</li>
</ul>
</div>
)
}
}
export default class App extends Component {
state = {
isShow: false,
}
render() {
return (
<div>
<Navbar
// 自定义一个事件(回调函数),让子组件通过触发这个事件来改变父组件的状态。
hideSidebar={() => {
this.handle()
}}
></Navbar>
{this.state.isShow && <Sidebar></Sidebar>}
</div>
)
}
handle() {
this.setState({
isShow: !this.state.isShow,
})
console.log('navbar组件触发父组件')
}
}
15. react 非父组件通信的几种方式
状态提升
,把需要传递的属性,存放在一个公共的 父组件中,然后获取.订阅与发布
context 模式
订阅与发布
- 发布函数要在所有 订阅函数 最后执行.
接收参数 subscribe
使用订阅函数传递参数 publish
使用发布函数
bus调度中心:
var bus = {
list: [], //用于存放 订阅的函数.
// 订阅函数 :每次调用 订阅函数的时候都往 list 中存入一个 回调函数
subscribe(callback) {
this.list.push(callback)
},
// 发布函数: 发布的时候,遍历 list 并逐个调用,调用回调函数的时候 可以给回调函数中传入 发布的参数.
publish(val) {
this.list.map((callback) => callback && callback(val))
},
}
bus.subscribe((val) => {
console.log('111', val)
})
bus.subscribe((val) => {
console.log('222', val)
})
bus.subscribe((val) => {
console.log('333', val)
})
bus.subscribe((val) => {
console.log('444', val)
})
setTimeout(() => {
bus.publish('我是发布者')
}, 0)
context 模式
- 首先全局创建一个 React.createContext()
- 然后根据
提供者(Provider)
消费者(Consumer)
的关系来,进行标签包裹- 使用
Provider 包裹
需要 组间通信组件的父组件
.- Provider 标签有一个value 属性, 可以给这个属性定义 一个函数,让子组件通过调用这个函数来改变父组件的状态.
context 标签的用法
1. provider 标签用法:
<GloablContext.Provider
value={{
name: 'coco',
info: this.state.detail,
changeInfo: (val) => {
this.setState({
detail: val,
})
},
}}
>
</GloablContext.Provider>
2. consumer 标签用法:
<GloablContext.Consumer>
{(val) => {
return (
)
}}
</GloablContext.Consumer>
import React, { Component } from 'react'
import axios from 'axios'
import '../01-base/css/communite.css'
// 全局创建 context 函数
const GloablContext = React.createContext()
class FilmItem extends Component {
render() {
let { synopsis } = this.props
return (
<GloablContext.Consumer>
{(val) => {
return (
<div
className="filmitem"
onClick={() => {
// 点击之后可以通过调用 provider 提供的changeInfo 方法来,改变父组件的状态
val.changeInfo(synopsis)
}}
>
<img src={this.props.poster} alt={this.props.name}></img>
<div>{this.props.name}</div>
</div>
)
}}
</GloablContext.Consumer>
)
}
}
class FilmDetail extends Component {
render() {
return (
<GloablContext.Consumer>
{(val) => {
return <div className="filmdetail">{val.info}</div>
}}
</GloablContext.Consumer>
)
}
}
export default class App extends Component {
constructor() {
super()
this.state = {
filmList: [],
detail: '',
}
axios.get('/test.json').then((res) => {
console.log(res.data.data.films)
this.setState({
filmList: res.data.data.films,
})
})
}
render() {
return (
<GloablContext.Provider
value={{
name: 'coco',
info: this.state.detail,
changeInfo: (val) => {
this.setState({
detail: val,
})
},
}}
>
<div>
{this.state.filmList.map((item) => (
<FilmItem {...item} key={item.filmId}></FilmItem>
))}
<FilmDetail></FilmDetail>
</div>
</GloablContext.Provider>
)
}
}
16. react 插槽的使用
- 在子组件中 使用 this.props.children 占位来接收从父组件传递的插槽。
- this.props.children 是一个数组,可以通过 this.props.children[0] 来固定展示自己想要展示的插槽.
import React, { Component } from "react";
class Child extends Component {
render() {
return (
<div>
//11111 22222 33333
{this.props.children}
// 33333 22222 11111
{this.props.children[2]}
{this.props.children[1]}
{this.props.children[0]}
</div>
);
}
}
export default class App extends Component {
render() {
return (
<div>
<Child>
<div>11111</div>
<div>22222</div>
<div>33333</div>
</Child>
</div>
);
}
}
17. react 生命周期
1. 初始化阶段
- componentWillMount()
- render()
- componentDidMount()
注意:
- componentWillMount 声明周期 在 react 16.8 版本以后就不被推荐了.(控制台会有报错提醒)
- 原因: 函数执行优先级不较低,会被高优先级的函数给打断.
- 解决办法: 使用 UNSAFE_componentWillMount() 可以解决告警提示.
2. 运行中阶段
- componentWillReceiveProps(nextProps)
- 该函数一般写在 子组件中.
- 该函数 会最先获取到父组件传递过来的属性,在获取到父组件传递过来的属性之后,可以进行 ajax 数据请求
- 也可以在该函数中将 父组件传递过来的属性转变为 自身组件的属性.
- shouldComponentUpdate(nextProps,nextState)
- scu 性能优化函数
- componentWillUpdate() : 16.8 版本后也被弃用了, 使用 UNSAFE_ 解决报错提醒.
- render()
- componentDidUpdate()
注意:
- componentDidUpdate() 声明周期函数的缺点就是会
多次调用
,每当状态更新的时候,该函数就会被调用.- componentDidUpdate(
prevProps,prevState
) 两个参数
- prevProps 表示老的属性.
- prevState 表示老的状态.
- shouldComponentUpdate(
nextProps,nextState
)
- nextProps 表示
新的属性
- nextState 表示
新的状态
3. 销毁阶段
- componentWillUnmount()
作用:在组件销毁时进行清除工作,比如计时器,事件监听器等.
import React, { Component } from "react";
class Child extends Component {
componentDidMount() {
window.onresize = () => {
console.log("resize");
};
this.timer = setInterval(() => {
console.log("111");
}, 1000);
}
render() {
return <div>child</div>;
}
componentWillUnmount() {
window.onresize = null;
clearInterval(this.timer);
console.log("子组件销毁了");
}
}
export default class App extends Component {
state = {
show: true,
};
render() {
return (
<div>
<button
onClick={() => {
this.setState({ show: !this.state.show });
}}
>
click
</button>
{this.state.show && <Child></Child>}
</div>
);
}
}
18. react 新生命周期
getDerivedStateFromProps(nextProps,nextState)
- 该方法再根组件中可以代替
componentWillMount()
生命周期函数。 - 在子组件中可以代替
componentWillReceiveProps()
生命周期函数。
- 该函数是 class 类中的静态方法,需要
用static关键字去定义.
- 该方法需要 return 一个空对象.
- 该方法再
初始化
的时候会调用一次
,数据更新
的时候也会去调用.
- 该方法可以
配合 componentDidUpdate()
方法,优化 axios请求
.
import React, { Component } from "react";
export default class App extends Component {
state = {
name: "coco",
};
static getDerivedStateFromProps(nextProps, nextState) {
console.log("getDerivedStateProps");
return {
name: nextState.name,
};
}
render() {
return (
<div>
<button
onClick={() => {
this.setState({ name: "tiechui" });
}}
>
click
</button>
{this.state.name}
</div>
);
}
}
getSnapshotBeforeUpdate()
- 该生命周期函数不能与
componentWillUpdate()
同时出现,控制台会报错.- 该生命周期函数可以用于获取 更新后 dom 元素的高度.
- 通过
componentDidUpdate(prevProps,prevState,value)
,这个生命周期函数的第三个值来获取 getSnapshotBeforeUpdate() 函数返回 dom 元素的高度.
import React, { Component } from "react";
export default class App extends Component {
state = {
name: "coco",
};
render() {
console.log("render");
return (
<div>
<button
onClick={() => {
this.setState({ name: "tiechui" });
}}
>
click
</button>
{this.state.name}
</div>
);
}
componentDidUpdate(prevProps, prevState, value) {
console.log("componentDidUpdate", value);
}
getSnapshotBeforeUpdate() {
console.log("getSnapShotBeforeUpdate");
return 100;
}
}
案例
import React, { Component } from "react";
// 需求,每点击添加的时候,就往box添加数据,但是显示的仍然是当前 查看元素的位置.
export default class App extends Component {
state = {
list: [1, 2, 3, 4, 5, 6, 7, 8, 9],
};
box = React.createRef();
render() {
return (
<div>
<button
onClick={() => {
this.setState({
list: [
...[11, 12, 13, 14, 15, 16, 17, 18, 19],
...this.state.list,
],
});
}}
>
click
</button>
<ul ref={this.box} style={{ height: "200px", overflow: "auto " }}>
{this.state.list.map((item) => (
<li key={item} style={{ height: "200px", background: "tomato" }}>
{item}
</li>
))}
</ul>
</div>
);
}
getSnapshotBeforeUpdate(prevProps, prevState) {
console.log(this.box.current.scrollHeight, "前");
return this.box.current.scrollHeight;
}
componentDidUpdate(prevProps, prevState, value) {
this.box.current.scrollTop += this.box.current.scrollHeight - value;
console.log(this.box.current.scrollHeight, "后");
}
}
19. react 性能优化
- shouldComponentUpdate()
- PureComponent()
import React, { PureComponent } from 'react'
export default class App extends PureComponent {
render(){
return ()
}
}
20. react hooks
- hooks 是针对函数式组件的,可以让 函数式组件拥有自己的状态.
1. useState() 声明函数式组件自己的状态
- 在 react 中 引入
useState
这个函数- 在
useState('初始状态')
,声明变量的初始状态.- const [text, setText] = useState(‘’)
- text 即为函数式组件自己的状态 , useState(‘’) 中的参数就是这个状态初始化的值.
- setText ,只可以通过这个 方法来改变 text 这个状态.
useState() 也是记忆函数
,可以记住当前的状态
import React, { useState } from "react";
export default function App() {
const [text, setText] = useState("");
const handleClick = () => {
setText("coco");
};
return (
<div>
<div> text:{text}</div>
<button onClick={handleClick}>click</button>
</div>
);
}
2. useEffect() 可以用于获取请求数据
- useEffect(()=>{},[]) 有两个参数.
第一个是回调函数
,第二个是依赖项
.- 如果在 useEffect 中对状态进行改变,则需要在 第二个依赖数组中 填写.否则,useEffect 中的状态改变时不会触发 useEffect 这个函数.(
通俗讲就是监听useEffect函数中的状态,如果发生改变则重新触发 useEffect 这个函数.
)- useEffect() 回调函数中可以再
return ()=>{}
返回一个回调函数,这个回调函数是用于组件销毁时,清除计时器 或者 清除监听事件
使用的.
使用注意:
- useEffect 可以多次注册,不是说只可以注册使用一次.
useEffect() 和 useLayoutEffect() 的区别
- 首先两个函数在执行结果上是一致的.
useLayoutEffect()
函数 就相当于 componentDidUpdate() 和 componentDidMount() 函数一样,都是在 react 执行完dom元素之后执行的(dom树)
.useEffect()
函数是在页面渲染完之后执行的(渲染树)
.
相关词汇:dom树
和渲染树
使用建议:
-
平时
推荐使用 useEffect(),可以减少代码阻塞
.
-
- 如果要对
dom进行操作
的时候,推荐使用 useLayoutEffect() 函数,可以减少回流和重绘
.
- 如果要对
3. useCallback(()=>{},[]) 用于缓存函数
- 在函数式组件中,
每当状态更新的时候,组件内定义的函数也会随着被重新定义
.这样会造成不必要的性能损耗.- 在
useCallback()
的第二个参数(数组)中,填入 该函数所依赖的状态(依赖项),然后 useCallback 就会把这个定义的函数给缓存下来,每当依赖项发生变化的时候,函数才会被重新定义
.- 该函数也是性能优化的一种方式.该函数会返回一个函数.
const handle = useCallback(() => {
first;
}, [second]);
4. useMemo() 类似与vue 中的计算属性.
- useMemo() 会返回一个函数的执行结果.
useMemo(() => () => {}, [second]);
5. useRef() 相当于React.createRef()
- 可以绑定 dom 元素,获取 dom 元素的相关属性.
- 可以保存 临时变量的值.
// 通过 useRef 保存临时变量
import React, { useState, useRef } from "react";
export default function App() {
const [count, setcount] = useState(0);
// 给 useRef 传入一个初始值0.
var mycount = useRef(0);
return (
<div>
<button
onClick={() => {
setcount(count + 1);
mycount.current++;
}}
>
add
</button>
{count}--{mycount.current}
</div>
);
}
6. useContext() 可以减少组件层级
- 提供(provider)参数的时候 还是要使用 标签来包裹需要组间通信的组件的.
- 子组件获取 参数的时候,通过 useContext() 来获取传递参数的对象.
- useContext(
React.createContext()
),参数要传入 react创建的 context函数.
// 全局创建 context 函数
const GloablContext = React.createContext()
// 提供参数的时候 还是要按照下面这种方式来写.
<GloablContext.Provider
value={{
name: 'coco',
info: this.state.detail,
changeInfo: (val) => {
this.setState({
detail: val,
})
},
}}
>
</GloablContext.Provider>
// 获取参数的时候 可以通过 useContext() 来获取
const value = useContext(GloablContext)
此时的value 值就是 provider value属性的值.
value = {
name: 'coco',
info: this.state.detail,
changeInfo: (val) => {
this.setState({
detail: val,
})
},
}
7. useReducer(reducer,initailState
) 用于复杂组件通信
useReducer()
和useContext()
配合使用.- 使用 useReducer() 函数,需要全局
声明一个 initailState 对象来存放公共状态.
- 在 声明一个 reducer(
prevState,actions
) 函数,用来存放 不同的处理逻辑.
- 将声明的
reducer()函数
和initailState对象
当作参数传入useReducer(reducer,initailState)
import React, { useReducer, useContext } from "react";
const reducer = (prevState, actions) => {
console.log(prevState, "prevState");
let newState = { ...prevState };
switch (actions.type) {
case "change-a":
newState.a = actions.value;
return newState;
case "change-b":
newState.b = actions.value;
return newState;
default:
return prevState;
}
};
const initailState = {
a: "1111",
b: "2222",
};
const GlobalContext = React.createContext();
export default function App() {
const [state, dispatch] = useReducer(reducer, initailState);
return (
<GlobalContext.Provider
value={{
state,
dispatch,
}}
>
<div>
<Child1></Child1>
<Child2></Child2>
<Child3></Child3>
</div>
</GlobalContext.Provider>
);
}
function Child1() {
const { dispatch } = useContext(GlobalContext);
return (
<div style={{ background: "tomato" }}>
<button onClick={() => dispatch({ type: "change-a", value: "改变a" })}>
change-a
</button>
<button onClick={() => dispatch({ type: "change-b", value: "改变b" })}>
change-b
</button>
</div>
);
}
function Child2() {
const { state } = useContext(GlobalContext);
return <div style={{ background: "yellow" }}>{state.a}</div>;
}
function Child3() {
const { state } = useContext(GlobalContext);
return <div style={{ background: "lightblue" }}>{state.b}</div>;
}
8. 自定义 hook 必须使用use定义
- 自定义 hook 可以用于抽离一些公共逻辑,用于复用.
21. react 路由
1. 安装
//安装 react 路由
npm i react-router-dom@5
2. 使用
- 从 'react-router-dom’组件中 引入
HashRouter Route
组件.- 使用 HashRouter 标签包裹 Route 标签.
- 在 Route 标签上 标注
path component
等属性进行路径和组件的映射.
import React, { Component } from "react";
import { HashRouter, Route, Redirect, Switch } from "react-router-dom";
import Films from "../views/films";
import Cinemas from "../views/cinemas";
import Center from "../views/center";
import NotFound from "../views/notFound";
export default class index extends Component {
render() {
return (
<HashRouter>
// switch 组件,当页面刷新的时候,使页面停留在当前路由组件.
<Switch>
<Route path="/films" component={Films}></Route>
<Route path="/cinemas" component={Cinemas}></Route>
<Route path="/center" component={Center}></Route>
// exact属性 表示精准匹配.
<Redirect from="/" to="/films" exact></Redirect>
<Route component={NotFound}></Route>
</Switch>
</HashRouter>
);
}
}
3. react 路由重定向(Redirect)
- 可以在没有指定路由路径的时候,重定向到我们想要指定的组件.
一般配合 Switch 组件
使用,因为单独使用 Redirect 组件,刷新页面的时候不会停留在当前组件,而会 重定向到 我们指定的路由.- Redirect 组件标签有一个
exact
属性,表示精准匹配,
再需要给组件添加 404 组件的时候,可以使用到exact
这个属性.- 如果只是写一个
from='/'
表示 模糊匹配,每当没有相匹配的路径时,都会重定向到我们指定的路由组件.
4. react 嵌套路由
- 在需要二级路由的地方,重新再写一套 路由跳转的方式.
//在 films 组件中嵌套二级路由.
import React, { Component } from "react";
import { Redirect, Route, Switch } from "react-router-dom";
// 引入films 的二级路由组件.
import Comingsoon from "./films/comingsoon";
import Nowplaying from "./films/comingsoon";
export default class films extends Component {
render() {
return (
<div>
films // 此时
当films组件渲染的时候,就可以根据路径的映射去渲染二级路由的组件.
<Switch>
<Route path="/films/nowplaying" component={Nowplaying}></Route>
<Route path="/films/comingsoon" component={Comingsoon}></Route>
<Redirect from="/films" to="/films/nowplaying"></Redirect>
</Switch>
</div>
);
}
}
5. react 声明式导航
- 从
react-router-dom
库中 引入 NavLink 组件.- 将 NavLink 组件标签写在
HashRouter 组件标签内
.(可以在封装的 路由组件中预留一个 插槽,用来放 NavLink)- NavLink 组件标签有一个
activeClassName 属性,可以高亮当前显示的路由.
import { HashRouter, Route, Redirect, Switch, NavLink } from "react-router-dom";
<HashRouter>
<NavLink activeClassName="cocoActive" to="/films">
影院
</NavLink>
<NavLink activeClassName="cocoActive" to="/center">
中心
</NavLink>
<NavLink activeClassName="cocoActive" to="/cinemas">
电影
</NavLink>
<Switch>
<Route path="/films" component={Films}></Route>
<Route path="/cinemas" component={Cinemas}></Route>
<Route path="/center" component={Center}></Route>
<Redirect from="/" to="/films" exact></Redirect>
<Route component={NotFound}></Route>
</Switch>
</HashRouter>;
6. react 编程式导航
- 函数式组件,通过 props.history.push(‘/path’)
- 类组件,通过 this.props.history.push(‘/path’)
- hooks, 引入 useHistory() 函数, const history = useHistory() ,history.push(‘/path’)
7. react 路由传参
- 动态路由传参 ,
需要
在 path 路径上留变量占位符
,刷新页面参数不会丢.
- query 传参 ,
不需要
在 path 路径上留变量占位符
,刷新页面参数会丢失.
- state 传参,
不需要
在 path 路径上留变量占位符
, 刷新页面参数会丢失.
1. 动态路由传参 (刷新页面,参数不会丢失)
// 路由标签 path
<Route path="/details/:id" component={Details}></Route>
// 路由跳转,携带参数
props.history.push(`/details/${id}`)
// 获取动态路由传递的参数
props.match.params.id
2. query路由传参 (刷新页面,参数会丢失)
<Route path="/details" component={Details}></Route>
// 路由跳转 携带参数.
props.history.push({pathname:'/details',query:{myid:id}})
// 获取 query 路由传递的参数
props.location.query.myid
3. state 路由传参 (刷新页面,参数会丢失)
<Route path="/details" component={Details}></Route>
// 路由跳转 携带参数.
props.history.push({pathname:'/details',state:{myid:id}})
// 获取 state 路由传递的参数
props.location.state.myid
8. react 路由拦截
(路由守卫)
- react 使用路由拦截的时候,需要使用 Route 组件标签的
render={(props)=>{}}
属性辅助.- 在 render 函数的回调函数中,使用 if 条件语句 或 三目运算符 进行跳转判断.
- 如果满足条件 进行路由跳转,如果不满足条件 使用 Redirect 组件标签进行 路由重定向.
注意:
使用 render()函数进行 路由拦截的时候,
回调函数内部返回的组件没有 props
,需要我们将render((props)=><Center {...props}>)
,render 函数 回调函数的 props 当作 属性传递给 组件.
// 注册 login 路由组件.
<Route path='/login' component={Login}></Route>
// 路由拦击 条件判断.
function isAuth() {
return localStorage.getItem('token')
}
// 路由拦截,注册路由
<Route
path="/center"
render={(props) => {
return isAuth() ? <Center {...props} /> : <Redirect to={"/login"}></Redirect>;
}}
></Route>
// login 组件 内容
<div>
<h1>登录页面</h1>
<input type='text'></input>
<button onClick={() => {
localStorage.setItem('token', '123')
this.props.history.push('/center')
}}>登录</button>
</div>
9. 路由模式
HashRouter
:带 # 号,但是不会发生 把路径作为参数向后端请求数据的情况.BrowserRouter
: 没有 # 号(好看),该种路由模式,会把 路径当作参数向后端发请求要页面
,如果后端没有对应路径的处理路径,就会报 404 ,不好看.
- 解决办法: 当因为后端没有对应路径处理路径而报 404 的时候,就让后端
刷新我们提供的 index.html 文件,即可.
10. withRouter
- 当 Route 包裹的路由组件 还有一个子组件的时候, 该路由组件的子组件是不能直接拿到 路由的相关 api 的.
- 解决办法:将父组件 Films 组件的
props 同样当作属性传给 子组件.
- 从 react-router-dom 库中 ,引入 withRouter 函数.
<Route to='/films' component={Films}>
// films 组件 ,我们可以将父组件 Films组件的 props 同样当作属性传给 子组件.
list.map(item=>{
<FilmsItem {...item} {...props}>
})
function FilmsItem (props){
<li onClick={()=>{
props.history.push('/detail')
}}></li>
}
// 第二种解决方法
import {withRouter} from 'react-router-dom'
list.map(item=>{
<withFilmItem {...item}>
})
function FilmsItem (props){
<li onClick={()=>{
props.history.push('/detail')
}}></li>
}
const withFilmItem = withRouter(FilmsItem)
向外暴露组件时,使用 withRouter 包裹.
import React, { Component } from "react";
import { withRouter } from "react-router-dom";
class center extends Component {
render() {
return <div>center</div>;
}
}
export default withRouter(center);
22. react 表单域组件(ref)
- react中可以通过 ref 来获取组件上的所有属性和方法。
1. 定义 ref
username = React.createRef()
2. 绑定 ref
<Field label="用户名" type="text" ref={this.username}></Field>
3. 点击获取 ref 组件上的所有属性
console.log(this.username.current)
23. react 中使用map 报错 return a value in arrow function
问题 : 使用 arr.map(item=> {} ) 时会报上面的错误。
原因: 在eslint 语法规范下 react 将map 中的 {} 花括号认为是 jsx 语法,所以出现了上面的错误。
解决办法: 将花括号
改为小括号
arr.map(item=> () )
24. react 中引入本地图片问题
参考文章 :
解决办法:
- import方法
- require方法
方法一:
import imgURL from './../images/photo.png';
...
...
<img src={imgURL } />
方法二:
<img src={require('./../images/photo.png')} />
25. react 反向代理
反向代理 和 正向代理
- 反向代理:隐藏服务端.
- 正向代理:隐藏客户端.
react 配置反向代理
npm install http-proxy-middleware --save
- 在 react 项目中的 src 目录下创建一个
setupProxy.js
文件- 然后再 setupProxy.js 文件中写入相关配置.
重启服务器
,每次修改完 setupProxy 文件之后都需要重启 服务,npm start.
const { createProxyMiddleware } = require("http-proxy-middleware");
module.exports = function (app) {
app.use(
"/api",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
})
);
// 可以配置多个跨域
app.use(
"/api2",
createProxyMiddleware({
target: "http://localhost:5000",
changeOrigin: true,
pathRewrite: {
"^/api": "",
},
})
);
};
26. react 解决 css选择器冲突问题
.
- 以
name.moudle.css
来命名 css 文件.- 在 js 文件中引入
import style from 'name.moudle.css'
- 获取 name.moudle.css 文件中的选择器,
style.选择器名称
name.moudle.css 文件的缺点
缺点:使用
标签名选择器
的时候 ,会影响到全局样式
.
解决办法:可以在每个大组件的最外面 起不同的类名,然后每在 使用标签名选择器的时候,在前面加上这个最外面的类名.
name.moudle.css文件:
.active{
color:red;
}
js文件获取:
import style from 'name.moudle.css'
<div className={style.active}></div>
27. react Redux
- npm i redux
- 创建一个 store.js 文件.
- 从 redux 中 引入 createStore( reducer )
npm i redux
store.js 文件:
import {createStore} from 'redux'
const reducer = (prevState,action)=>{
return prevState
}
const store = createStore( reducer )
export default store
使用 redux
相关方法:
- store.dispatch()
- store.subscribe()
- store.getState()
// 传递参数的文件
import store from '../store.js'
useEffect(()=>{
store.dispatch({
type:'giveState'
})
return {
store.dispatch({
type:'detoryGiveState'
})
}
})
// 接收参数的文件
import store from '../store'
// 每次 redux ,通过 dispatch 触发action的时候,都会触发 store.subscribe() 方法.
// 在回调函数中 可以通过 store.getState() 方法来获取 redux更新后的最新数据.
store.subscribe(()=>{
console.log(store.getState())
})
redux 简单实现原理
function createCocoStore(reducer) {
let list = []
let state = reducer(undefined, {})
function dispatch(action) {
state = reducer(state, action)
for (let i in list) {
list[i] && list[i]()
}
}
function subscribe(callback) {
list.push(callback)
}
function getState() {
return state
}
return {
dispatch,
subscribe,
getState,
}
}
纯函数
- 对函数外界没有副作用.
- 同样的输入得到同样的输出.
28. react Redux-reducer合并
- 创建不同的 js 文件,用来管理不同的 reducer.
- 在 store.js 文件中 导入这些管理 reducer 的文件.
- 从 redux 中导入
combineReducers
函数.(用于不同 reducer 的合并)
import { combineReducers } from "redux";
import functionA from "./functionA.js";
import functionB from "./functionB.js";
import functionC from "./functionC.js";
const reducer = combineReducers({
a: functionA,
b: functionB,
c: functionC,
});
// 获取状态:
store.getState().a.状态名;
29. react actionCreater
- 创建不同的 js 文件, 然后在声明的函数中返回一个对象。
- 最后暴露这个声明的函数。
30. react redux-thunk
redux-thunk 用于解决请求异步数据的问题
npm i redux-thunk
- 在 store.js 文件中,从 redux 中引入 ‘applyMiddleware()’;
- 引入 ‘redux-thunk’ .
- 在 store 上挂载.
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'
const store = createStore(applyMiddleware(reduxThunk))
用法: function aa() {
return (dispatch) => {
axios.get('/test').then(() => {
dispatch({
type: 'test',
list: res.data.data,
})
})
}
}
31. react redux-promise
npm i redux-promise
- 跟 redux-thunk 功能类似,都是用于异步获取数据.
- redux-thunk 返回一个回调函数; redux-promise 返回一个 promise.
import { createStore, applyMiddleware } from 'redux'
import reduxThunk from 'redux-thunk'
import reduxPromise from 'redux-promise'
const store = createStore(applyMiddleware(reduxThunk, reduxPromise))
用法: function aa() {
return axios.get('/test').then(() => {
return {
type: 'test',
list: res.data.data,
}
})
}
32. react 取消 redux 订阅
- 声明一个变量,接收订阅返回的函数.
- 在组件销毁的时候,调用上面接收订阅返回的函数.
useEffect(()=>{
var destorySubscribe = store.subscribe(()=>{})
return {
// 取消订阅
destorySubscribe()
}
})
33. react react-redux
npm i react-redux
相关方法和属性
- provider 标签,包裹 跟组件.有一个 store 属性,需将引入的 store 当作属性值传递给该属性.
- connect() 函数,在暴露组件时用于包裹当前组件,该函数有两个参数
第一个参数
: 是一个回调函数
,函数中需要返回一个对象,用于给将来的子组件传递属性
.第二个参数
: 是一个对象
,用于给将来的子组件传递 函数(方法)
- 从 ‘react-redux’ 中引入 Provider , 并使用 <Provider> 标签包裹 App 组件.
- 在 App 组件中, 引入
connect
函数 (从react-dux
中引入),在暴露组件的时候使用connect()(App)
函数进行包裹.- 该方法可以不用每次都手动的去
订阅
和取消订阅
。
import store from './redux/store.js'
import Provider from 'react-redux'
;<Provider store={store}>
<App />
</Provider>
App组件: import { connect } from 'react-redux'
function Test() {
return <div></div>
}
function a() {}
function b() {}
// connect 有两个参数,第一是回调函数 用于给将来的子组件传递属性,第二个回调函数是一个对象,用于给将来的子组件传递方法(函数.)
export default connect(
() => {
return {
a: 属性值a,
b: 属性值b,
}
},
{
fnA: a,
fnB: b,
}
)(Test)
react-redux 原理
function cocoConnect(cb, obj) {
var value = cb()
return (Mycomponent) => {
return (props) => {
return <Mycomponent {...value} {...props} {...obj}></Mycomponent>
}
}
}
34. react redux持久化
import { createStore } from 'redux'
import { persistStore, persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage' // defaults to localStorage for web
import rootReducer from './reducers'
const persistConfig = {
key: 'root',
storage,
}
const persistedReducer = persistReducer(persistConfig, rootReducer)
export default () => {
let store = createStore(persistedReducer)
let persistor = persistStore(store)
return { store, persistor }
}
// index.js 文件中引入 persistGate 网关
import { PersistGate } from 'redux-persist/integration/react'
// ... normal setup, create store and persistor, import components etc.
const App = () => {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<RootComponent />
</PersistGate>
</Provider>
);
};
35. react 支持装饰器语法
安装相关插件
npm i @babel/core @babel/plugin-proposal-decorators @babel/preset-env
创建.babelrc 文件
{ "presets": [ "@babel/preset-env" ],"plugins": [ [ "@babel/plugin-proposal-decorators", { "legacy": true } ] ] }
创建 config-overrides.js
const path = require('path') const { override, addDecoratorsLegacy } = require('customize-cra') function resolve(dir) { return path.join(__dirname, dir) }const customize = () => (config, env) => { config.resolve.alias['@'] = resolve('src') if (env === 'production') { config.externals = { 'react': 'React', 'react-dom': 'ReactDOM' } }return config };module.exports = override(addDecoratorsLegacy(), customize())
安装依赖
npm i customize-cra react-app-rewired
修改 package.json
"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test", "eject": "react-app-rewired eject" },
在vscode 中打开下面的配置,使vscode对 装饰器语法不报错
36. react styled-components
- npm i styled-component
- import styled from ‘styled-component’
import styled from "styled-component";
const styledFooter = styled.footer`
background: yellow;
border: 1px solid black;
ul {
li {
color: red;
}
}
&:hover {
background: pink;
}
`;
<styledFooter>
<ul>
<li></li>
</ul>
</styledFooter>;
37. react redux-saga
相关方法
- take() : 监听触发 dispatch 的函数
- fork() : 执行异步处理函数
- call() : 发出异步请求,并获得异步请求的返回值.
- put() : 发起新的 dispatch()
saga 简单使用
1. app组件: 点击发起dispatch
<button onClick={
() => {
if (store.getState().list.length === 0) {
store.dispatch({
type: 'get-list'
})
} else {
console.log('读取缓存', store.getState().list);
}
}
}>
请求ajax1
</button>
2. store文件:
import { createStore, applyMiddleware } from 'redux'
//添加 saga 中间件.
import createSagaMiddleware from 'redux-saga'
import watchSaga from './saga';
import reducer from './reducers'
// 创建 saga对象,并使用 applyMiddleware() 中间件进行包裹
const SagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(SagaMiddleware))
// 监听 创建好的 saga任务.
SagaMiddleware.run(watchSaga)
export default store
3. saga.js文件:
import {takeEvery, take, fork, put, call , all} from "redux-saga/effects";
import watchSaga1 from './saga1'
import watchSaga2 from './saga2'
function* watchSaga() {
// 监听多个 saga 文件,并执行
yield all([watchSaga1(),watchSaga2()])
// while (true) {
// // 该部分代码等同于 下面的 takeEvery()
// // take 监听触发dispatch的函数
// // fork 同步执行异步处理函数
// yield take("get-list");
// yield fork(getList);
// }
yield takeEvery('get-list',getList)
}
function* getList() {
// 处理异步函数
// call() 发出异步请求
let res = yield call(getListAction);
// put() 发出新的 action
yield put({
type: "change-list",
payload: res,
});
}
function getListAction() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(["111", "2222", "333"]);
}, 2000);
});
}
export default watchSaga;
38. react portal
- 可以将指定 dom 元素节点放在自己指定的地方.
createPortal()
从 react-dom 引入该函数
- 第一个参数:需要传送的 dom 元素.
- 第二个参数:需要传送到的位置.
import { createPortal } from "react-dom";
function App() {
return createPortal(<div></div>, document.body);
}
39. react 懒加载
- react 的懒加载(.lazy)属性需要配合 Suspense 标签使用。
import React, { Suspense } from 'react'
// 正常引入 js组件
// import Lazy1 from './lazy1'
// import Lazy2 from './lazy2'
// 懒加载引入 js组件
const Lazy1 = React.lazy(() => import('./lazy1'))
const Lazy2 = React.lazy(() => import('./lazy2'))
function Test() {
return (
<div>
<Suspense fallback={<div>正在加载...</div>}>
{this.type == 1 ? <Lazy1></Lazy1> : <Lazy2></Lazy2>}
</Suspense>
</div>
)
}
40. react forwardRef
- forwardRef 函数可以透传,拿到子组件中的 dom 元素。
- forwardRef 需要从 react 中引入。
- forwardRef
函数需要传入一个 回调函数
, 该回调函数有两个参数
- 参数一:从父组件传递的 props 属性。
- 参数二:从父组件传递的 ref 属性。
import React, { Component, forwardRef } from 'react'
export default class app extends Component {
mytext = React.createRef()
render() {
return (
<div>
<button
onClick={() => {
this.mytext.current.focus()
this.mytext.current.value = ''
console.log(this.mytext.current)
}}
>
获取焦点
</button>
<Child ref={this.mytext}></Child>
</div>
)
}
}
const Child = forwardRef((props, ref) => {
return (
<div style={{ background: 'tomato' }}>
<input ref={ref} defaultValue={'1111'}></input>
</div>
)
})
41. react memo 函数式组件优化
- 类似于 类组件中
pureComponent()
函数钩子,但是 pureComponent 函数只能用在 类组件中.memo()
需要从 react 中引入 ,使用再函数式组件中.
- 使用该函数创建的子组件,
当父组件传递给子组件的属性发生变化的时候,子组件(函数式组件)才会重新渲染.
import React, { Component, memo } from 'react'
export default class app extends Component {
state = {
name: 'coco',
text: '原始',
}
render() {
return (
<div>
<button
onClick={() => {
this.setState({
name: 'ike',
})
}}
>
修改
</button>
<button
onClick={() => {
this.setState({
text: '修改',
})
}}
>
修改2
</button>
<div>{this.state.name}</div>
<Child text={this.state.text}></Child>
</div>
)
}
}
const Child = memo((props) => {
console.log(1111)
return <div style={{ background: 'tomato' }}>{props.text}</div>
})