前言
React是一个用于构建用户界面的javaScript库,起源于facebook的内部项目,在13年f进行开源
17版本官网:React – A JavaScript library for building user interfaces
18版本官网:React 官方中文文档
特点:
- 声明式编码,组件化编码能提高开发效率和组件复用性
- React Native 编写原生应用
- 高效(优秀的Diffing算法)
一、基础
主要核心,依赖下面四个文件<!-- 引入核心库。全局出现React对象--> <script type="text/javascript" src="./React-js/16.8/react.development.js"></script> <!-- 用于支持react操作DOM。全局出现ReactDOM对象--> <script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script> <!-- 用于将jsx转换为js --> <script text="text/javascript" src="./React-js/16.8/babel.min.js"></script> <!-- 用于对组件标签属性进行限制。全局出现PropTypes对象 --> <script src="./React-js/16.8/prop-types.js"></script>
1、基本使用
1.1、虚拟dom
关于虚拟DOM:
- 本质是object类型的对象(一般对象)
- 虚拟DOM比较“轻”,真实DOM比较“重”,因为虚拟DOM是React内部在用,无需真实DOM 上:那么多的属性。
- 虚拟DOM最终会被React转化为真实DOM。呈现在页面上
<body>
<div id="test"></div>
<!-- 引入核心库 -->
<script type="text/javascript" src="./React-js/16.8/react.development.js"></script>
<!-- 用于支持react操作DOM -->
<script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script>
<!-- 用于将jsx转换为js -->
<script text="text/javascript" src="./React-js/16.8/babel.min.js"></script>
<!-- 一定是babel -->
<script type="text/babel">
// 创建虚拟dom
const VDOM = <h1>Hello.React</h1>
const VDOM2 = <h1>----------------</h1>
// 渲染虚拟DOM到页面(后面的会替换之前)
ReactDOM.render(VDOM,document.getElementById('test'))
ReactDOM.render(VDOM2,document.getElementById('test'))
</script>
</body>
2.2、JSX的语法规则
1、全称: JavaScript XML。
2、react定义的一种类似于XML的JS扩展语法: JS + XML本质是React.createElement(component, props, ...children)方法的语法糖
3、作用: 用来简化创建虚拟DOM
写法:var ele = <h1>Hello JSX!</h1>
注意1:它不是字符串, 也不是HTML/XML标签
注意2:它最终产生的就是一个JS对象
- 定义虚拟DOM时,不要写引号。
- 标签中混入JS表达式时要用{}-
- 样式的类名指定不要用class,要用className.
- 内联样式,要用style={{key : value}}的形式去写。
- 只有一个根标签
- 标签必须闭合
- 标签首字母
- (1).若小写字母开头,则将改标签转为htm1中同名元素,若htm1中无该标签对应的同名元素,则报错。
- (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错。
<body>
<div id="test"></div>
<!-- 引入核心库 -->
<script type="text/javascript" src="./React-js/16.8/react.development.js"></script>
<!-- 用于支持react操作DOM -->
<script text="text/javascript" src="./React-js/16.8/react-dom.development.js"></script>
<!-- 用于将jsx转换为js -->
<script text="text/javascript" src="./React-js/16.8/babel.min.js"></script>
<!-- js写法 -->
<script type="text/javascript">
// 创建虚拟dom
const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'js写法'))
// 渲染虚拟DOM到页面(后面的会替换之前)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
<!-- jsx写法 -->
<script type="text/babel">
const data = ['抽烟','喝酒','烫头']
const obj = {name1:'抽烟',name2:'喝酒',name3:'烫头'}
const myId = 'song'
const myData = 'HELLO'
const VDOM = (
<div>
<h1 className="box" id={myId}>
<span style={{ color: 'red', fontSize: '40px' }}>{myData.toLocaleLowerCase()}</span>
</h1>
<input type="text" />
<ul>
{
// data // 直接使用数组,会自动遍历
// obj // 对象,会报错
data.map((item,i)=><li key={i}>{item}</li>)
}
</ul>
</div>
)
// 渲染虚拟DOM到页面(后面的会替换之前)
ReactDOM.render(VDOM, document.getElementById('test'))
</script>
</body>
2、函数式组件
<script type="text/babel">
// 定义函数组件
function Demo() {
console.log(this); // 经过babel转化开启严格模式,this没有明确的调用,所以为undefined
return <h2>函数定义的组件</h2>
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
/*
执行了ReactDOM.render( <MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOW转为真实DOM,随后呈现在页面中。
*/
</script>
3、类组件
<script type="text/babel">
// 定义类组件
class Demo extends React.Component{
render(){
// render:类的原型对象上(可在浏览器控制台输入Demo回车测试),供实例使用
// this指向Demo的实例对象。俗称:组件对象或组件实例对象
console.log('render中this',this);
return (
<h2>类定义的组件</h2>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Demo />, document.getElementById('test'))
/*
执行了ReactDOM.render( <MyComponent/>.......之后,发生了什么?
1.React解析组件标签,找到了MyComponent组件。
2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用原型上的render方法
3.将返回的虚拟DOW转为真实DOM,随后呈现在页面中。
*/
</script>
3.1、constructor
<script type="text/babel">
// 定义类组件
class Weather extends React.Component {
// 构造器是否接受props,是否传递super,取决于:是否需要在构造器中通过this访问props
// 若是写了构造器,不给super传props则,在构造器中拿不到props。为undefined
constructor(props) { // 构造器只调用一次
// super(props)
super()
console.log(this.props);
}
render() {
return (
<h2 ></h2>
)
}
}
const p = { name: 'tom', age: 18, sex: '女' }
// 渲染组件到页面
ReactDOM.render(<Weather {...p} />, document.getElementById('test'))
</script>
4、组件的三大核心
4.1、state:存放状态
<script type="text/babel">
// 定义类组件
class Weather extends React.Component {
constructor(props) { // 构造器只调用一次
super(props)
// 初始化状态
this.state = {
isHot: false,
wind: '大风'
}
// 改变原型上的demo的this指向,并把原型上的demo赋值到实例上的demo上。处理下面this为undefind
this.demo = this.demo.bind(this)
}
render() { // 调用1+n次。1:初始化 n:状态更新的次数
return (
<h2 onClick={this.demo}>今天天气{this.state.isHot ? '炎热' : '很冷'}</h2>
)
}
demo() {
// demo放在哪里? Weather的原型对象上,供实例使用
//由于demo是作为onclick的回调,所以不是通过实例调用的,是直接调用
//类中的方法默认开启了局部的严格模式,所以demo中的this为undefined
console.log('this', this); // undefind
// 不能直接修改值。数据虽然变化,但页面不刷新
// this.state.isHot = !this.state.isHot
// 注意:需要通过setState方法来修改状态
this.setState({ isHot: !this.state.isHot })
}
}
// 渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
function demo() {
// console.log('被点击');
alert('被点击')
}
</script>
(1)、state简写
<script type="text/babel">
// 定义类组件
class Weather extends React.Component {
// 初始化状态
state = { isHot: false, wind: '大风' }
render() {
return (
<h2 onClick={this.demo}>今天天气{this.state.isHot ? '炎热' : '很冷'}</h2>
)
}
// 自定义方法---需要赋值语句的形式+箭头函数
demo = ()=> {
console.log('this', this); // undefind
this.setState({ isHot: !this.state.isHot })
}
}
// 渲染组件到页面
ReactDOM.render(<Weather />, document.getElementById('test'))
</script>
<!--
1、组件中 render方法中的this 为组件实例对象-
2、组件自定义的方法中this为 undefined,如何解决?
a.强制绑定this:通过函数对象bind
b.箭头函数
3、状态数据,不能直接修改或更新
-->
4.2、props:接收参数
<script type="text/babel">
// 定义类组件
class Weather extends React.Component {
render() {
console.log(this);
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age}---{this.props.flag}</li>
</ul>
)
}
}
const p = { name: 'tom', age: 18, sex: '女' }
// 渲染组件到页面
ReactDOM.render(<Weather {...p} flag={666}/>, document.getElementById('test'))
</script>
(1)、props限制
<!-- 用于对组件标签属性进行限制。全局出现PropTypes对象 -->
<script src="./React-js/16.8/prop-types.js"></script>
<script type="text/babel">
// 定义类组件
class Weather extends React.Component {
render() {
console.log(this);
// 注意:props是只读的
this.props.speak()
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age+1}-----</li>
</ul>
)
}
}
// propTypes:给类加规则
Weather.propTypes = {
// 在15以及以下版本
// name:React.PropTypes.string
// 16版本及以上,需要通过引入PropTypes对象
name:PropTypes.string,
sex:PropTypes.string,
age:PropTypes.number.isRequired, // isRequired。必传
speak: PropTypes.func // 限制为函数
}
// 设置不传时的默认值
Weather.defaultProps = {
sex:'我是默认值'
}
// 渲染组件到页面
ReactDOM.render(<Weather name="song" age={666} speak={fun}/>, document.getElementById('test'))
function fun (){
console.log('我是函数');
}
</script>
(2)、简写
<script type="text/babel">
class Weather extends React.Component {
// 写在类里面,相当于给类加属性
static propTypes = {
name: PropTypes.string,
sex: PropTypes.string,
age: PropTypes.number.isRequired, // isRequired。必传
speak: PropTypes.func // 限制为函数
}
static defaultProps = {
sex: '我是默认值'
}
render() {
console.log(this);
// 注意:props是只读的
this.props.speak()
return (
<ul>
<li>姓名:{this.props.name}</li>
<li>性别:{this.props.sex}</li>
<li>年龄:{this.props.age + 1}-----</li>
</ul>
)
}
}
// 渲染组件到页面
ReactDOM.render(<Weather name="song" age={666} speak={fun} />, document.getElementById('test'))
function fun() {
console.log('我是函数');
}
</script>
(3)、在函数组件的使用
// 定义函数组件
function Weather(props) {
console.log(this, props); // 经过babel转化开启严格模式,this没有明确的调用,所以为undefined
return (
<ul>
<li>姓名:{props.name}</li>
<li>性别:{props.sex}</li>
<li>年龄:{props.age + 1}-----</li>
</ul>
)
}
Weather.propTypes = {
name: PropTypes.string,
sex: PropTypes.string,
age: PropTypes.number.isRequired,
}
Weather.defaultProps = {
sex: '我是默认值'
}
// 渲染组件到页面
ReactDOM.render(<Weather name="song" age={666} />, document.getElementById('test'))
(4)、在脚手架中使用需要单独下载
4.3、refs与事件处理
(1)、字符串形式的ref
class Demo extends React.Component{
// 展示左侧输入框的数据
showData = ()=>{
alert(this.refs.inp1.value)
}
// 展示左侧输入框的数据
showData2 = ()=>{
alert(this.refs.inp2.value)
}
render(){
return(
<div>
<input ref="inp1" type="text" placeholder="点击按钮提示数据"/>
<button onClick={this.showData}>点击提示左侧的数据</button>
<input ref="inp2" onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>
</div>
)
}
}
(2)、回调函数形式的ref
class Demo extends React.Component {
state = { isHot: true }
// 展示左侧输入框的数据
showData = () => {
alert(this.inp1.value)
}
changWeacter = () => this.setState({ isHot: !this.state.isHot })
/**
* ref若是以下面内联函数的方式定义,它在更新过程中会别执行两次,
* 第一次传入参数为null,第二次才是DOM元素
* 因为每次渲染时都会创建新的函数实例,所以React清空旧的ref,被设置新的、
* *
* 不过可以把回调函数定义成class的绑定函数的方式可以避免,
*
*/
saveInp = (c)=>{
this.inp2 = c; console.log('绑定函数@', c)
}
render() {
const { isHot } = this.state
//
return (
<div>
<h1>今天天气{this.state.isHot ? '炎热' : '很冷'}</h1>
{/* c:input标签。相当于往Demo身上添加了inp1属性,把input标签赋值给它。内联函数方式*/}
<input ref={c => { this.inp1 = c; console.log('@', c) }} type="text" placeholder="点击按钮提示数据" />
{/* 使用class绑定函数的方式 */}
<input ref={this.saveInp} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧的数据</button>
<button onClick={this.changWeacter}>点击切换天气</button>
</div>
)
}
}
(3)、API形式的ref
class Demo extends React.Component {
/** React.createRef
* 存放被ref标识的节点,但只能单独存一个
*/
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框的数据
showData = () => {
console.log(this.myRef,this.myRef2);
}
render() {
//
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<input ref={this.myRef2} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧的数据</button>
</div>
)
}
}
5、事件处理
class Demo extends React.Component {
/**
* 1、通过onXxx属性指定事件处理函数(注意大小写)
* (1)React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 ---为了处理兼容
* (2)React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) --- 为了高效
* 2、通过event.target得到发生事件的DOM元素对象
*/
myRef = React.createRef()
myRef2 = React.createRef()
// 展示左侧输入框的数据
showData = () => {
console.log(this.myRef, this.myRef2);
}
showData2 = (e) => {
console.log(e.target.value);
}
render() {
//
return (
<div>
<input ref={this.myRef} type="text" placeholder="点击按钮提示数据" />
<button onClick={this.showData}>点击提示左侧的数据</button>
<input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据" />
</div>
)
}
}
5.1、函数柯里化与高阶函数
/**
* *高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
* 1.若A雨数,接收的参数是一个函数,那么A就可以称之为高阶函数。
* 2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
* 常见的高阶函数有: Promise、setTimeout、arr.map()等等
*
* *函数的柯里化: 通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。
*/
// 普通写法
function sum(a,b,c){
return a+b+c
}
const res = sum(1,2,3)
// 柯里化写法
function sum(a){
return (b)=>{
return (c)=>{
return a+b+c
}
}
}
const res = sum(1)(2)(3)
/* 普通写法: */
class Demo extends React.Component {
state = {
username:'',
password:''
}
// 保存
saveUsername = (e)=>{
this.setState({username:e.target.value})
}
savePassword = (e)=>{
this.setState({password:e.target.value})
}
// 展示左侧输入框的数据
handleSubmit = (e)=>{
e.preventDefault() // 阻止默认行为
const {username,password } = this.state
console.log('用户名密码:',username,password);
}
render() {
//
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveUsername} type="text" />
密码:<input onChange={this.savePassword} type="password" />
<button>登录</button>
</form>
)
}
}
/* 柯里化写法: */
class Demo extends React.Component {
state = {
username: '',
password: ''
}
// 保存。柯里化写法
saveData = (dataType) => {
console.log('dataType', dataType);
return (e) => {
this.setState({ [dataType]: e.target.value })
}
}
handleSubmit = (e) => {
e.preventDefault() // 阻止默认行为
const { username, password } = this.state
console.log('用户名密码:', username, password);
}
render() {
//
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={this.saveData('username')} type="text" />
密码:<input onChange={this.saveData('password')} type="password" />
<button>登录</button>
</form>
)
}
}
/* 不使用柯里化写法: */
class Demo extends React.Component {
state = {
username: '',
password: ''
}
// 保存。
saveData = (dataType, data) => {
console.log('dataType', dataType, data);
this.setState({ [dataType]: data })
}
handleSubmit = (e) => {
e.preventDefault() // 阻止默认行为
const { username, password } = this.state
console.log('用户名密码:', username, password);
}
render() {
return (
<form onSubmit={this.handleSubmit}>
用户名:<input onChange={(e) => this.saveData('username', e.target.value)} type="text" />
密码:<input onChange={(e) => this.saveData('password', e.target.value)} type="password" />
<button>登录</button>
</form>
)
}
}
6、生命周期
6.1、旧版(16版本以及以前)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
1.constructor():构造器
2.componentWillMount():组件初始化挂载前
3.render():初始调用一次,每次更改状态都会调用
4.componentDidMount():组件挂载完
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
1.shouldComponentUpdate()
2.componentWillUpdate()
3.render()
4.componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
1.componentWillUnmount():组件卸载前调用
(1)、执行案例
class Count extends React.Component {
constructor(props) {
console.log('constructor--构造器');
super(props)
// 初始化状态
this.state = { count: 0 }
}
add = () => {
this.setState({ count: this.state.count + 1 })
}
force = () => {
// 不受shouldComponentUpdate影响
this.forceUpdate()
}
// 卸载组件
unload = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 组件初始化挂载前
componentWillMount() {
console.log('componentWillMount-组件初始化挂载前');
}
// 组件初始化挂载完
componentDidMount() {
console.log('componentDidMount');
}
// 组件卸载前
componentWillUnmount() {
console.log('componentWillUnmount');
}
/* 更新流程 */
// 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
shouldComponentUpdate() {
console.log('shouldComponentUpdate----------控制组件更新阀门');
return true
}
// 组件更新前
componentWillUpdate() {
console.log('componentWillUpdate------------组件更新前');
}
// 组件更新完
componentDidUpdate() {
console.log('componentDidUpdate-------------组件更新完');
}
render() {
console.log('render');
const { count } = this.state
return (
<div>
<h2>当前的求和为:{count}</h2>
<button onClick={this.add}>点击+1</button>
<button onClick={this.force}>强制更新</button>
<button onClick={this.unload}>卸载</button>
</div>
)
}
}
ReactDOM.render(<Count />, document.querySelector('#test'))
(2)、父子组件Render
// 父组件
class A extends React.Component {
state = { carName: '奔驰' }
chang = () => {
this.setState({ carName: '牛马' })
}
render() {
return (
<div>
<h2>A</h2>
<button onClick={this.chang}>换车</button>
<B carName={this.state.carName}></B>
</div>
)
}
}
// 子组件
class B extends React.Component {
// 第一次不会执行,只有更新后才执行
componentWillReceiveProps(props) {
console.log('componentWillReceivProps', props);
}
/* 更新流程 */
// 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
shouldComponentUpdate() {
console.log('shouldComponentUpdate----------控制组件更新阀门');
return true
}
// 组件更新前
componentWillUpdate() {
console.log('componentWillUpdate------------组件更新前');
}
// 组件更新完
componentDidUpdate() {
console.log('componentDidUpdate-------------组件更新完');
}
render() {
console.log('render');
return (
<div>
<h2>B---{this.props.carName}</h2>
<div></div>
</div>
)
}
}
ReactDOM.render(<A />, document.querySelector('#test'))
6.2、新版(17版本已经之后)
1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
- constructor()
- getDerivedStateFromProps
- render()
- componentDidMount()
2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
- getDerivedStateFromProps:俗称派生钩子
- shouldComponentUpdate()
- render()
- getSnapshotBeforeUpdate:俗称快照钩子
- componentDidUpdate()
3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
- componentWillUnmount()
(1)、getDerivedStateFromProps
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
// 如果返回一个对象,则相当于修改了state,
// return props
// return null
}
默认返回 null
该钩子会导致代码冗余,并使组件难以维护,很少使用。
若state的值在任何时候都取决于props,那么可以使用该生命周期钩子。
(2)、getSnapshotBeforeUpdate
可在componentDidUpdate钩子第三个参数接受到返回值
class Count extends React.Component {
constructor(props) {
console.log('constructor----------',props);
super(props)
// 初始化状态
this.state = { count: 0 }
}
add = () => {
this.setState({ count: this.state.count + 1 })
}
force = () => {
// 不受shouldComponentUpdate影响
this.forceUpdate()
}
// 卸载组件
unload = () => {
ReactDOM.unmountComponentAtNode(document.getElementById('test'))
}
// 若state的值在任何时候都取决于props,那么可以使用该生命周期钩子
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps', props, state);
// 如果返回一个对象,则相当于修改了state,不在具有响应式
// return props
return null
}
// 俗称快照。可在componentDidUpdate钩子第三个参数接受到返回值
getSnapshotBeforeUpdate(){
console.log('getSnapshotBeforeUpdate');
return null
// return 'song'
}
// 组件初始化挂载前----------18已经弃用
// componentWillMount() {
// console.log('componentWillMount-----组件初始化挂载前');
// }
componentDidMount() {
console.log('componentDidMount------------------------组件初始化挂载完');
}
componentWillUnmount() {
console.log('componentWillUnmount----------------组件卸载前');
}
/* 更新流程 */
// 不写默认返回true。控制组件更新,需要返回true才能执行后面更新操作
shouldComponentUpdate() {
console.log('shouldComponentUpdate--------------更新阀门');
return true
}
// 组件更新前----------18已经弃用
// componentWillUpdate() {
// console.log('componentWillUpdate------------组件更新前');
// }
componentDidUpdate(preProps,preState,snapshotValue) { // (旧的,旧的,快照返回值)
console.log('componentDidUpdate-------------组件更新完',preProps,preState,snapshotValue);
}
render() {
console.log('render----------------',this);
const { count } = this.state
return (
<div>
<h2>当前的求和为:{count}</h2>
<button onClick={this.add}>点击+1</button>
<button onClick={this.force}>强制更新</button>
<button onClick={this.unload}>卸载</button>
</div>
)
}
}
ReactDOM.render(<Count count={66} />, document.querySelector('#test'))
(3)、getSnapshotBeforeUpdate案例
每秒出现一条数据,但页面里面内容不根据超出部分滚动
class NewList extends React.Component {
state = {
newsArr: []
}
componentDidMount() {
setInterval(() => {
const { newsArr } = this.state
// 模拟一条新数据
const news = '新闻' + (newsArr.length + 1)
// es6语法,添加进行
this.setState({ newsArr: [news, ...newsArr] })
}, 1000)
}
getSnapshotBeforeUpdate() {
// 获取内容整体高度
return this.refs.list.scrollHeight
}
componentDidUpdate(preProps, preState, height) {
console.log(height);
this.refs.list.scrollTop += this.refs.list.scrollHeight - height
}
render() {
return (
<div className="list" ref="list">
{
this.state.newsArr.map((el, i) => {
return <div key={i} className="news">{el}</div>
})
}
</div>
)
}
}
ReactDOM.render(<NewList count={66} />, document.querySelector('#test'))
6.3、总结
- 16版本:正常使用
- 17版本:componentWillMount,componentWillUpdate,componentWillReceiveProps可以使用,但要在前加修饰符。例如:UNSAFE_componentWillUpdate
- 18版本:componentWillMount,componentWillUpdate,componentWillReceiveProps被移除多,出两个新钩子getDerivedStateFromProps,getSnapshotBeforeUpdate
7、扩展
7.1、react/vue中的key有什么作用?(key的内部原理)?
简单:key是虚拟DOM对象的标识,在更新显示时key起着极其重要的作用。
详细:当状态中的数据发生变化时,react会根据【新数据】生成【新的虚拟DOM】,随后React进行【新虚拟DOM】与【旧虚拟DOM】的diff比较,比较规则如下:
1.旧虚拟DOM中找到了与新虚拟DOM相同的key:
a.若虚拟DOM中内容没变,直接使用之前的真实DOM
b.若虚拟DOM中内容变了,则生成新的真实DOM,替换页面中之前的真实DOM
2.旧虚拟DOM中未找到与新虚拟DOM相同的key
a.根据数据创建新的真实DOM,随后渲染到到页面
(1)、为什么遍历列表时,key最好不要用index?
/* * 1。若对数据进行:逆序添加、逆序删除等破坏顺序操作,会产生没有必要的真实DOM更新==>界面效果没问题,但效率低。
* 2.如果结构中还包含输入类的DOM:会产生错误DOM更新==>界面有问题。
*
* 案例:本来只要更新小李这条数据的,因为使用索引做key,导致所有都要更新
* 初始化数据:
* { id: 1, name: '小张', age: 18 },
* { id: 2, name: '小宋', age: 19 }
* 初始化虚拟DOM:
* <li key=0 >小张--18</li>
* <li key=1 >小张--19</li>
* 更新后的数据:往前面增加数据
* { id: 3, name: '小李', age: 66 },
* { id: 1, name: '小张', age: 18 },
* { id: 2, name: '小宋', age: 19 }
* 初始化虚拟DOM:
* <li key=0 >小李--66</li>
* <li key=1 >小张--18</li>
* <li key=2 >小张--19</li>
* */
class Person extends React.Component {
state = {
persons: [
{ id: 1, name: '小张', age: 18 },
{ id: 2, name: '小宋', age: 19 }
]
}
add = () => {
const { persons } = this.state
const p = { id: persons.length + 1, name: '小李', age: persons.length + 1 }
this.setState({ persons: [p,...persons] })
}
render() {
return (
<div>
<h2>展示人员信息</h2>
<button onClick={this.add}>添加人员小李</button>
<h2>使用索引值做key</h2>
<ul>
{
this.state.persons.map((obj, i) => {
return <li key={i}>{obj.name}--{obj.age}<input type="txt" /></li>
})
}
</ul>
<h2>使用id做key</h2>
<ul>
{
this.state.persons.map((obj, i) => {
return <li key={obj.id}>{obj.name}--{obj.age}<input type="txt" /></li>
})
}
</ul>
</div>
)
}
}
// 渲染虚拟DOM到页面(后面的会替换之前)
ReactDOM.render(<Person />, document.getElementById('test'))
二、脚手架
安装遇到的问题:
场景:create-react-app 项目名 创建项目卡住,过一会报下面错误【error An unexpected error occurred: "https://registry.npm.taobao.org/axios: certificate has expired"】
原因:淘宝镜像原地址证书已于2024年1月22日过期
现已更换镜像地址为
https://registry.npmmirror.com
【npm config set registry https://registry.npmmirror.com】
整体技术架构:react+webpack+es6+eslint
- 全局安装: npm install -g create-react-app
- 切换对应目录使用命令创建:create-react-app 项目名
- 进入项目文件夹: cd 项目名
- 启动:npm start
温馨提示:脚手架里面导入JS和JSX文件可以不写后缀
1、基本目录结构
public ---- 静态资源文件夹
favicon.icon ------ 网站页签图标
index.html -------- 主页面
logo192.png ------- logo图
logo512.png ------- logo图
manifest.json ----- 应用加壳的配置文件
robots.txt -------- 爬虫协议文件
src ---- 源码文件夹
App.css -------- App组件的样式
App.js --------- App组件
App.test.js ---- 用于给App做测试
index.css ------ 样式
index.js ------- 入口文件
logo.svg ------- logo图
reportWebVitals.js --- 页面性能分析文件(需要web-vitals库的支持)
setupTests.js ---- 组件单元测试的文件(需要jest-dom库的支持)
2、模块化
2.1、组件的模块化
2.2、样式的模块化
(1)、方式一:使用module
(2)、方式二:使用scss、less等
3、配置代理
3.1、方式一:不能配置多个代理
优点:配置简单,前端请求资源时可以不加任何前缀。
注意:当前脚手架端口为3000。5000为服务器端口
在package.json文件中添加配置【 "proxy":"http://localhost:5000"】
在页面中请求服务器数据,必须把5000的端口改为3000,因为我们是在3000发送到3000,另一个3000的数据是从5000获取的。
另外这种方式不会把所有请求都转发都服务器,它把脚手架中public目录当根路径,会先在这个里面找,没有才会通过另一个代理的3000端口访问端口为5000的服务器
3.2、方式二:创建代理配置文件
在src下创建配置文件:src/setupProxy.js
优点:可以配置多个代理,可以灵活的控制请求是否走代理。
缺点:配置繁琐,前端请求资源时必须加前缀。
注意:在新版中使用旧版的配置方法:localhost会拒绝连接。无法访问
// 【旧版】
// const proxy_ = require('http-proxy-middleware')
// module.exports = function (app) {
// app.use(
// proxy_('/api1', {
// target: 'http://localhost:5000',
// changeOrigin:true, // 默认false
// pathRewrite:{'^/api1':''}
// })
// )
// }
// 【新版】
const { createProxyMiddleware } = require('http-proxy-middleware') // 分开暴露的方式
module.exports = function (app) {
app.use(
createProxyMiddleware('/api1', { // 遇见【/api1】前缀的请求,触发此代理配置
target: 'http://localhost:5000', // 请求转发给谁
/** 控制服务器收到的请求头中Hoos字段的值。默认为false
* 前端代理加上changeOrigin:true,后台收到的为 localhost:5000 否则为 localhost:3000
*/
changeOrigin: true,
pathRewrite: { '^/api1': '' }, // 重写请求路径。匹配路径中存在/api1的,替换为空
}),
createProxyMiddleware('/api2', {
target: 'http://localhost:5001',
changeOrigin: true,
pathRewrite: { '^/api2': '' },
})
)
}
4、发布与订阅
- 下载: pubsub-js
使用:
- 引入:import PubSub from 'pubsub-js'
- 订阅:this.aa = PubSub.subscribe('dataObj',(_,data)=>{})
- 发布:PubSub.publish('dataObj',{isLoading:false, err:err.message })
- 取消订阅:PubSub.unsubscribe(this.aa)
三、react路由
react-router-dom在2021年11月升级到了6版本
1、旧版本【5】
npm i react-router-dom@5
(1)、HashRouter 与 BrowserRouter
HashRouter
- 基于
hash
模式:页面跳转原理是使用了location.hash
、location.replace
,和vue router
的hash
模式实现一致。- 比较丑:在域名后,先拼接
/#
,再拼接路径,也就是利用锚点,实现路由的跳转。如:www.dzm.com- #后面的不会发送到服务器。可以解决一些路径问题
- 刷新页面会丢失路由state参数,没保存,
BrowserRouter
- 基于
history
模式:页面跳转原理是使用了HTML5
为浏览器全局的history
对象新增了两个API
,包括history.pushState
、history.replaceState
,和vue router
的history
模式实现一致。- 更加优雅: 直接拼接路径。如:http://www.dzm.com/xx
- 后端需做请求处理:切换路由后,请求接口路径会发生变化,后端需要配合做处理。
- 兼容:低版本浏览器可能不支持,目前市面上热门浏览器应该都支持了,不是特殊情况可以放心使用。(不兼容IE9及以下版本)
- 刷新页面对路由state参数没影响,因为state保存在history对象里面
(2)、封装NavList标签
(3)、精准匹配与模糊匹配(默认)。重定向。Switch标签
精准匹配不要随便开启,需要再开,有些时候开启会导致无法继续匹配二级路由
(4)、样式丢失问题。
问题复现:想在所有路由路径前面加固定前缀!
原因:react通过webpack中的devServer 在脚手架中的public为根路径开启一个内置服务器,请求啥给啥,如果没有会把index.html返回。
由此得知【http://localhost:3000/song/css/bootstrap.css】没有这个/song的目录
解决方案:
1、修改index.html
2、修改入口文件。
因为地址会出现#号,#号后面的会认为是前端资源不会在请求服务器
(5)、嵌套路由
(6)、路由传参
params 参数:参数在地址栏显示。
{/* 一、传递params参数 */} <Link to={`/home/message/detail/${msgObj.id}/${msgObj.title}`}>{msgObj.title}</Link> {/* 声明params参数 */} <Route path="/home/message/detail/:id/:title" component={Detail}></Route> // 接收params参数 const { match: { params: { id, title } } } = this.props
search 参数:参数在地址栏显示。
{/* 二、传递search参数(类似Ajax的query) */} <Link to={`/home/message/detail/?id=${msgObj.id}&title=${msgObj.title}`}>{msgObj.title}</Link> {/* 声明search参数,无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> import qs from 'querystring' // 以前自带,现在需要下载 // 接收search参数 const { search } = this.props.location const { id, title } = qs.parse(search.slice(1)) // 转码
state 参数:参数不在地址栏显示。
{/* 三、传递state参数 */} <Link to={{ pathname: '/home/message/detail', state: { id: msgObj.id, title: msgObj.title } }}>{msgObj.title}</Link> {/* 声明state参数,无需声明接收 */} <Route path="/home/message/detail" component={Detail}></Route> // 接收state参数 const { id, title } = this.props.location.state
(7)、编程式路由导航
借用history对象的api来操作路由的跳转、前进、后退
{/* 一、传递params参数 */} this.props.history.push(`/home/message/detail/${id}/${title}`) {/* 二、传递search参数(类似Ajax的query) */} this.props.history.push(`/home/message/detail/?id=${id}&title=${title}`) {/* 三、传递state参数 */} this.props.history.push(`/home/message/detail`,{ id, title})
(8)、withRouter
作用:可在一般组件中使用路由组件的API。接收一个组件,返回一个新组件
withRouter
使用:
// 引入 import { withRouter } from 'react-router-dom' class MyNavLink extends Component { render() { return ( <div> <NavLink activeClassName="song" className="list-group-item" {...this.props} /> </div> ) } } // 使用 export default withRouter(MyNavLink)
2、新版【6】
- 内置组件的变化:移除<Switch />,新增<Routes />等
- 语法的变化:component={About} 变为 element={<About />} 等
- 新增多个hook:useParams、useNavigate、useMatch 等
- 官网推荐使用函数式组件,以后可能完全替代类式组件,现官网案例都是用函数式组件
(1)、一级路由【Routes、Route】,重定向【Navigate】
- <Routes> 和 <Route>要配合使用,且必须用<Routes>包裹<Route>标签
- <Route> 相当于if语句,其路径与当前URL匹配,呈现对应组件
- <Route caseSensitive>:该属性指定匹配时是否区分大小写(默认为false)
- 当URL发送变化时,<Routes>都会查看其所有子<Route>元素以找到最佳匹配
- <Route>也可嵌套使用,可配合useRoutes()配置“路由表”,但需通过<outlet>组件来渲染其子路由。
Navigate:只要该组件被渲染,就会修改路径,切换视图
其中属性replace:默认push,
import React from 'react'
import { NavLink,Routes,Route, Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'
export default function App() {
return (
<div>
// ...
<div className="list-group">
<NavLink className="list-group-item" to="/about">About</NavLink>
<NavLink className="list-group-item" to="/home">Home</NavLink>
</div>
// ...
<div className="panel-body">
<Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/home" element={<Home />}></Route>
{/* 重定向。Navigate不仅可以写to,还可通过replace指定调转模式 */}
<Route path="/" element={<Navigate to="/home" />}></Route>
</Routes>
</div>
// ...
</div>
)
}
(2)、自定义高亮类名
通过函数方式接收内置的返回值
import React from 'react'
import { NavLink,Routes,Route, Navigate } from 'react-router-dom'
import About from './pages/About'
import Home from './pages/Home'
export default function App() {
function handleClass({isActive}){
console.log('isActive',isActive);
return isActive ? 'list-group-item song':'list-group-item'
}
return (
<div>
// ...
<div className="list-group">
<NavLink className={handleClass} to="/about">About</NavLink>
<NavLink className={handleClass} to="/home">Home</NavLink>
</div>
// ...
</div>
)
}
(3)、路由表
useRoutes:通过该Hook函数创建路由表
import React from 'react'
import { Routes,Route, useRoutes } from 'react-router-dom'
import routes from './routes/index'
export default function App() {
// 根据路由表创建路由规则
const element = useRoutes(routes)
return (
<div>
// ...
<div className="panel-body">
{/* <Routes>
<Route path="/about" element={<About />}></Route>
<Route path="/home" element={<Home />}></Route>
<Route path="/" element={<Navigate to="/home" />}></Route>
</Routes> */}
{element}
</div>
// ...
</div>
)
}
// pages/routes/index.js
import { Navigate } from 'react-router-dom'
import Home from "../pages/Home"
import About from "../pages/About"
export default [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />
},
{
path: '/',
element: <Navigate to="/home" />
}
]
(4)、嵌套路由【Outlet】
Outlet:产生嵌套时,指定子路由的渲染位置
/* 【父】 */
import { useRoutes } from 'react-router-dom'
import routes from './routes/index'
export default function App() {
// 根据路由表创建路由规则
const element = useRoutes(routes)
return (
<div>
{/* ... */}
<div className="list-group">
<NavLink className="list-group-item" to="/about">About</NavLink>
{/* end:如果选择子级路由子级高亮,父级就不会高亮了 */}
<NavLink className="list-group-item" end to="/home">Home</NavLink>
</div>
{/* ... */}
<div className="panel-body">
{element}
</div>
{/* ... */}
</div>
)
}
// pages/routes/index.js
import { Navigate } from 'react-router-dom'
import Home from "../pages/Home"
import About from "../pages/About"
import News from '../pages/News'
import Message from '../pages/Message'
export default [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />,
children:[
{
path:'news',
element: <News />
},
{
path:'message',
element: <Message />
}
]
},
{ // 重定向
path: '/',
element: <Navigate to="/home" />
}
]
/* 【子】 */
import React, { useState } from 'react'
import { NavLink, Outlet } from 'react-router-dom'
export default function Home() {
return (
<div>
<h3>我是Home的内容</h3>
<div>
<ul className="nav nav-tabs">
<li>
{/* to="/home/news" 可以不带父级路由路径 */}
<NavLink className="list-group-item" to="news">News</NavLink>
</li>
<li>
<NavLink className="list-group-item" to="message">Message</NavLink>
</li>
</ul>
{/* 指定路由的位置 */}
<Outlet />
</div>
</div>
)
}
(5)、路由传参
- useParams():接收params参数,对应5版本中match.params
- useSearchParams():用于读取或修改当前位置的URL中的查询字符串。返回一个包含两个值的数组,分别为当前search参数,更新search参数
- useLocation():获取当前location信息,对应5版本路由组件的loction属性
- useMatch():返回当前匹配信息,对应5版本路由组件的match属性
/* 【传递参数】 */
import React, { useState } from 'react'
import { Link, Outlet } from 'react-router-dom'
export default function Message() {
const [message] = useState([
{ id: '01', title: '消息1', content: '吃' },
{ id: '02', title: '消息2', content: '喝' },
{ id: '03', title: '消息3', content: '嫖' },
{ id: '04', title: '消息4', content: '赌' }
])
return (
<div>
<ul>
{
message.map((m) => {
return (
<li key={m.id}>
{/* params参数 */}
{/* <Link to={`details/${m.id}/${m.title}`}>{m.title}</Link> */}
{/* Search参数 */}
{/* <Link to={`details?id=${m.id}&title=${m.title}`}>{m.title}</Link> */}
{/* state参数 */}
<Link to={`details`} state={{
id: m.id,
title: m.title
}}>{m.title}</Link>
</li>
)
})
}
</ul>
<hr />
<Outlet></Outlet>
</div>
)
}
/* 【接收参数】 */
import React from 'react'
import { useParams, useMatch, useSearchParams, useLocation } from 'react-router-dom'
export default function Details() {
/* params参数 */
// const {id,title} = useParams() // 方式一
// const a = useMatch('/home/message/details/:id/:title') // 方式二
// console.log('a',a);
/* Search参数: */
// // 方式一 search:参数 setSearch:更新参数方法
// const [search, setSearch] = useSearchParams()
// console.log('search', search.get('id'));
// const id = search.get('id')
// const title = search.get('title')
// // 方法二
// const a = useLocation()
// console.log(a);
/* state参数: */
const { state: { id, title } } = useLocation()
return (
<div>
<h3>展示详情</h3>
<ul>
{/* <li><button onClick={() => setSearch('id=666&title=更新哦~')}>点击更新收到的search参数</button></li> */}
<li>{id}</li>
<li>{title}</li>
</ul>
</div>
)
}
// pages/routes/index
export default [
{
path: '/about',
element: <About />
},
{
path: '/home',
element: <Home />,
children:[
{
path:'news',
element: <News />
},
{
path:'message',
element: <Message />,
children:[
{
// path:'details/:id/:title', // params 参数
path:'details', // Search与state 参数
element: <Details />
}
]
}
]
},
{
path: '/',
element: <Navigate to="/home" />
}
]
(6)、编程式路由导航【useNavigate】
import React, { useState } from 'react'
import { Link, Outlet, useNavigate } from 'react-router-dom'
export default function Message() {
const [message] = useState([
{ id: '01', title: '消息1', content: '吃' },
{ id: '02', title: '消息2', content: '喝' },
{ id: '03', title: '消息3', content: '嫖' },
{ id: '04', title: '消息4', content: '赌' }
])
const a = useNavigate()
function showDetail(m) {
// a('/about') // 方案一
a('/home/message/details',{ // 方案二。配置项暂时只支持下面两个
replace:false,
state:{
id:m.id,
title:m.title
}
})
}
return (
<div>
<ul>
{
message.map((m) => {
return (
<li key={m.id}>
{/* state参数 */}
<Link to={`details`} state={{
id: m.id,
title: m.title
}}>{m.title}</Link>
<button onClick={()=>showDetail(m)}>查看详情</button>
</li>
)
})
}
</ul>
<hr />
<Outlet></Outlet>
</div>
)
}
(7)、其它Hook
(7.1)、useInRouterContext()
// 判断当前是否在路由的上下文中
console.log('判断当前是否在路由里',useInRouterContext());
(7.2)、useNavigationType()
// 判断当前页面通过 【刷新(POP) 或 push(PUSH) 或 replace(REPLACE)】 进入到该页面的
console.log('useNavigationType',useNavigationType());
(7.3)、useOutlet()
/** 呈现当前组件中渲染的嵌套路由组件
* 如果嵌套路由没有挂载,则返回null
* 如果嵌套路由已经挂载,则展示嵌套的路由对象
*/
console.log('useOutlet',useOutlet());
(7.4)、useResolvedPath()
/** 解析路由信息
*
*/
console.log('useResolvedPath',useResolvedPath('/user?id=6&name=对对对#qwe'));
四、redux
安装:yarn add redux
中文文档: Redux | Redux 中文文档
作用:
- redux是一个专门用于做状态管理的JS库(不是react插件库)。
- 它可以用在react, angular, vue等项目中, 但基本与react配合使用
- 集中式管理react应用中多个组件共享的状态。(组件通信)
- 能不用就不用, 如果不用比较吃力才考虑使用。
1、简单使用
src目录下创建redux目录
(1)、count.js文件
本质是处理数据的函数。
第一次调用时,是store自动触发的,传递的preState是undefined
const initState = 0
// preState:旧值 action:UI组件传递过来的数据
function countRedux(preState = initState, action) {
// 从action对象中获取type,data
const { type, data } = action
// 根据type决定如何加工数据
switch (type) {
case 'add':
return preState + data
case 'minus':
return preState - data
default:
return preState
}
}
export default countRedux
(2)、store.js文件是redux的入口文件
/**
* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 引入
import { createStore } from "redux";
import countRedux from './count'
//
export default createStore(countRedux)
(3)、在src目录下index.js中进行store的监听
// 引入react核心库
import React from 'react'
// 引入ReactDom
import ReactDOM from 'react-dom'
// 引入App组件
import App from './App'
import store from './redux/store'
// 渲染App到页面
ReactDOM.render(
<App></App>
, document.getElementById('root'))
// 检测redux中状态的变化,只要变化,就调用render
store.subscribe(() => {
ReactDOM.render(
<App></App>
, document.getElementById('root'))
})
(4)、在页面中使用
import React, { Component } from 'react'
import store from '../../redux/store'
export default class Count extends Component {
// componentDidMount(){
// // 检测redux中状态的变化,只要变化,就调用render
// store.subscribe(()=>{
// this.setState({})
// })
// }
add = () => {
const { value } = this.selectNumber
// 通知redux改数据
store.dispatch({ type: 'add', data: value * 1 })
}
// ......
}
2、完整使用
与上面没很大区别只是多了两个文件
(1)、constanit文件
用于定义常量,便于管理的同时防止单词写错
/** * 该模块是用于定义action对象中type类型的常量值。便于管理的同时防止单词写错 */ export const ADD = 'add' export const MINUS = 'minus'
(2)、count_action文件
为Count组件生成action对象
import { ADD, MINUS } from './constant' export const addAction = data => ({ type: ADD, data }) export const minusAction = data => ({ type: MINUS, data })
(3)、count_reducer文件
import { ADD, MINUS } from './constant' const initState = 0 function countRedux(preState = initState, action) { // 从action对象中获取type,data const { type, data } = action // 根据type决定如何加工数据 switch (type) { case ADD: return preState + data case MINUS: return preState - data default: return preState } } export default countRedux
(4)、组件中使用
import React, { Component } from 'react' import store from '../../redux/store' import { addAction, minusAction } from '../../redux/count_action' export default class Count extends Component { add = () => { const { value } = this.selectNumber // 通知redux改数据。【*1 强制类型转换】 store.dispatch(addAction(value * 1)) } // ...... }
3、异步action
下载中间件:yarn add redux-thunk
中间件版本区分:
1、版本二【2.4.2】
2、版本三【3.1.1】
(1)、在redux的入口文件store使用
/**
* 该文件专门用于暴露一个store对象,整个应用只有一个store对象
*/
// 引入createstore,专门用于创建redux中最为核心的store对象
import { createStore, applyMiddleware, legacy_createStore } from "redux";
// 引入为Count组件服务的reducer
import countRedux from './count_reducer'
// 引入redux-thunk.川于支持异步action
// import reduxThunk from 'redux-thunk' // redux-thunk@2
import { thunk } from 'redux-thunk' // redux-thunk@3
// 暴露
export default legacy_createStore(
countRedux,
applyMiddleware(thunk)
)
(2)、action文件中
/**
* 该文件专门为Count组件生成action对象
*/
import store from './store' // 可省略
// 同步action,就是指action的值为一般对象
import { ADD} from './constant'
export const addAction = data => ({ type: ADD, data })
// 异步action,就是指action的值为函数
export const asyncAction = (data,time) => {
return ()=>{
setTimeout(()=>{
store.dispatch(addAction(data))
},time)
}
}
// 简写
// export const asyncAction = (data,time) => {
// return (dispatch)=>{ // store会自动调用这个,可以少引入store
// setTimeout(()=>{
// dispatch(addAction(data))
// },time)
// }
// }
(3)页面中
store.dispatch(asyncAction(value * 1,500))
五、react-redux
安装:yarn add react-redux
1、明确两个概念:
(1)、UI组件:不能使用任何redux的api,只负责页面的呈现、交互等。
(2)、.容器组件:负责和redux通信,将结果交给UI组件。2、注意:容器组件中的store是靠props传进去的,而不是在容器组件中直接引入
1、基本使用
第一步创建容器组件
// 引入Count的UI组件
import CountUI from '../../components/Count'
// 引入store
// import store from '../../redux/store'
import { addAction, minusAction, asyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
/* 【一般写法:】 */
function a(state) {
return { count: state }
}
function b(dispatch) { // 内置处理好了
return {
jia: (data) => dispatch(addAction(data * 1)),
jian: (data) => dispatch(minusAction(data * 1)),
asyncJia: (data,date) => dispatch(asyncAction(data * 1,date))
}
}
/** 创建并暴露一个Count的容器组件
* a:映射状态,返回值是一个对象
* b:映射操作状态的方法,返回值是对象
* connect自动帮我们传递了store,所以无需上面的引用
*/
export default connect(a, b)(CountUI)
/* 【简写:】 */
export default connect(
state => ({ count: state }),
/** 简写。 react-reudx 在API层面做了优化,帮我们处理dispatch */
{
jia: addAction,
jian: minusAction,
asyncJia: asyncAction
}
)(CountUI)
第二步注册容器组件
第三步,可以取消src目录下index.js的监听,因为插件内置监听了
第四步,在UI组件使用
// 通过this.props 可以取到
this.props.jia(value * 1)
2、完整用法
与上面基本相同,只是把容器组件和UI组件写在一起了
import React, { Component } from 'react'
import { addAction, minusAction, asyncAction } from '../../redux/count_action'
// 引入connect用于连接UI组件与redux
import { connect } from 'react-redux'
// 定义UI组件
class Count extends Component {
add = () => {
const { value } = this.selectNumber
// 通知redux改数据。【*1 强制类型转换】
this.props.jia(value * 1)
}
addOdd = () => {
const { value } = this.selectNumber
if (this.props.count % 2 != 0) this.props.jia(value * 1)
}
addAsync = () => {
const { value } = this.selectNumber
this.props.asyncJia(value * 1,1000)
}
render() {
return (
<div>
<h1>当前求和为:{this.props.count}</h1>
<select ref={c => this.selectNumber = c}>
<option value="1">1</option>
<option value="2">2</option>
</select>
<button onClick={this.add}>+</button>
<button onClick={this.addOdd}>当前求和为奇数在加</button>
<button onClick={this.addAsync}>异步加</button>
</div>
)
}
}
// 创建并暴露一个Count的容器组件
export default connect(
state => ({ count: state }), // 映射状态
{ // 映射操作状态的方法
jia: addAction,
jian: minusAction,
asyncJia: asyncAction
}
)(Count)
3、多个组件共享数据
(1)、combineReducers
汇总多个reducer
(2)、组件中使用
总reducer会交给store
4、redux开发者工具
在 Chrome 应用商店 中搜索redux下载
运行工具需要下载库:yarn add redux-devtools-extensino
在redux目录下的store.js使用
补充
1、setState的两种写法
(1)、函数式
setState(updater, [callback])
- updater可以接收到state和props。
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
(2)、对象式
setState(stateChange, [callback])
- 是函数式的简写(语法糖)
- callback是可选的回调函数, 它在状态更新、界面也更新后(render调用后)才被调用。
export default class setState extends Component {
state = { count: 0 }
add = () => {
const { count } = this.state
// 对象式
// this.setState({ count: count + 1 })
this.setState({ count: count + 1 },()=>{
console.log('count-回调',this.state.count);
})
console.log('count-外层',count);
// 函数式
// this.setState((state, props) => {
// console.log(state, props);
// return { count: state.count + 1 }
// })
}
render() {
return (
<div>
<h1>当前求和为:{this.state.count}</h1>
<button onClick={this.add}>点击加一</button>
</div>
)
}
}
2、路由懒加载与Suspense标签
- 通过React的lazy函数配合import()函数动态加载路由组件,路由组件代码会被分开打包
- 通过<Suspense>指定在加载得到路由打包文件前显示一个自定义loading界面
3、Fragment
可以不用再页面多渲染一个真实的DOM节点,与vue中template类似
4、context
用于祖孙组件间通信
场景:在应用开发中一般不用context, 一般都用它的封装react插件
import React, { Component } from 'react'
// 创建Context对象
const MyContext = React.createContext()
const { Provider,Consumer } = MyContext
// 类组件
export default class A extends Component {
state = { name: 'tom' }
render() {
return (
<div>
<h2>我是A组件</h2>
<h4>我的用户名是:{this.state.name}</h4>
<Provider value={{ name: this.state.name, age: '48' }}>
<B></B>
</Provider>
</div>
)
}
}
class B extends Component {
render() {
return (
<div>
<hr />
<h2>我是B组件</h2>
<C></C>
</div>
)
}
}
// 【类组件中使用】
// class C extends Component {
// // 声明接收祖组件的数据
// static contextType = MyContext
// render() {
// console.log('this-C', this.context);
// return (
// <div>
// <hr />
// <h2>我是C组件</h2>
// <h4>我的用户名是:{this.context.name}---{this.context.age}</h4>
// </div>
// )
// }
// }
// 【函数组件中使用】
function C() {
return (
<div>
<h2>我是函数式-C组件</h2>
<h4>我的用户名是:</h4>
<Consumer>
{
value =>{
// console.log(value);
return `${value.name},年龄是${value.age}`
}
}
</Consumer>
</div>
)
}
5、HookS
Hook是React 16.8.0版本增加的新特性/新语法。了解详情
作用:可让在函数组件中使用 state 以及其他的 React 特性
三个常用:
- React.useState():让函数组件也可以有state状态, 并进行状态数据的读写操作
- React.useEffect():用于模拟类组件中的生命周期钩子
- React.useRef():进行ref标识
import React, { Component } from 'react'
import ReactDOM from 'react-dom'
function HookFun() {
const [count, setCount] = React.useState(0) // 定义状态
const [name, setName] = React.useState('抽烟') // 定义状态
// 加的回调
function add() {
setCount(count + 1) // 写法一
// setCount(count => count + 1) // 写法二
}
// 加的回调
function hobby() {
setName('喝酒')
}
// 卸载定时器回调
function unmount(){
ReactDOM.unmountComponentAtNode(document.getElementById('root'))
}
// 相当于componentDidMount,页面初始化
// React.useEffect(() => {
// console.log('componentDidMount');
// }, [])
// 相当于componentDidUpdate,监听name的改变
// React.useEffect(() => {
// console.log('componentDidUpdate');
// }, [name])
// 相当于componentDidMount,卸载
React.useEffect(() => {
let timer = setInterval(() => {
// setCount(count + 1)
setCount(count => count+1)
// console.log('卸载。。。');
}, 1000)
return () => clearInterval(timer)
}, [])
// 创建ref
const myRef = React.useRef()
function show(){
console.log('myRef',myRef.current.value);
alert(myRef.current.value)
}
return (
<div>
<input type="text" ref={myRef} />
<h1>当前求和为:{count}</h1>
<h1>爱好:{name}</h1>
<button onClick={add}>点击加一</button>
<button onClick={hobby}>点击改名</button>
<button onClick={unmount}>卸载定时器回调</button>
<button onClick={show}>点击提示输入框数据</button>
</div>
)
}
export default HookFun
6、组件性能优化
缺陷:
只要执行setState(),即使不改变状态数据, 组件也会重新render() ==> 效率低
只当前组件重新render(), 就会自动重新render子组件,纵使子组件没有用到父组件的任何数据 ==> 效率低
思路:只有当组件的state或props数据发生改变时才重新render()。借助shouldComponentUpdate钩子
(1)、方案一:重写shouldComponentUpdate
比较新旧state或props数据, 如果有变化才返回true, 如果没有返回false
export default class A extends Component {
state = { name: 'tom', age: 28 }
change = ()=>{
this.setState({ name: 'JOB', age: 38 })
// this.setState({})
}
shouldComponentUpdate(nextProps,nextState){
console.log('A---shouldComponentUpdate',nextProps,nextState);
return !(this.state.name === nextState.name)
}
render() {
console.log('A组件-render',this.state.name);
return (
<div>
<h2>我是A组件</h2>
<h4>我的用户名是:{this.state.name}</h4>
<button onClick={this.change}>点击切换用户</button>
{/* <B age={this.state.age}></B> */}
<B age="99"></B>
</div>
)
}
}
class B extends Component {
shouldComponentUpdate(nextProps,nextState){
console.log('B---shouldComponentUpdate',nextProps,nextState);
return !(this.props.age === nextProps.age)
}
render() {
console.log('B组件-render',this.props.age);
return (
<div>
<hr />
<h2>我是B组件---年龄为:{this.props.age}</h2>
</div>
)
}
}
(2)、方案二:使用PureComponent
PureComponent重写了shouldComponentUpdate(), 只有state或props数据有变化才返回true
注意:
- 只是进行state和props数据的浅比较, 如果只是数据对象内部数据变了, 返回false
- 不要直接修改state数据, 而是要产生新数据。项目中一般使用PureComponent来优化
import React, { Component, PureComponent } from 'react'
export default class A extends PureComponent {
state = { name: 'tom', age: 28,stus:['吃','喝','嫖'] }
change = ()=>{
this.setState({ name: 'JOB', age: 38 })
// this.setState({})
/* 赋值给obj:是引用地址的传递,PureComponent不会进行render */
// const obj = this.state
// obj.name = '卡拉米'
// console.log(obj === this.state);
// this.setState(obj)
}
addStu = ()=>{
const {stus} = this.state
/* 无效 */
// stus.unshift('赌')
// this.setState({stus})
this.setState({stus:['堵',...stus]})
}
render() {
console.log('A组件-render',this.state.name);
return (
<div>
<h2>我是A组件</h2>
<h4>我的用户名是:{this.state.name}</h4>
<h4>数组:{this.state.stus}</h4>
<button onClick={this.change}>点击切换用户</button>
<button onClick={this.addStu}>往数组添加一个新属性</button>
{/* <B age={this.state.age}></B> */}
<B age="99"></B>
</div>
)
}
}
class B extends PureComponent {
render() {
console.log('B组件-render',this.props.age);
return (
<div>
<hr />
<h2>我是B组件---年龄为:{this.props.age}</h2>
</div>
)
}
}
7、renderProps(类似Vue插槽)
Vue中:
- 使用slot技术, 也就是通过组件标签体传入结构 <A><B/></A>
React中:
- 使用children props: 通过组件标签体传入结构
- 使用render props: 通过组件标签属性传入结构,可携带数据,一般用render函数属
export default class A extends Component {
render() {
return (
<div>
<h2>我是A组件</h2>
{/* <B>对对对</B> */}
{/* <B>
<C></C>
</B> */}
<B aa={(age) => <D age={age} />} render={(name) => <C name={name} />} />
</div>
)
}
}
class B extends Component {
state = { name: 'tom',age:'18' }
render() {
console.log('B:', this.props);
return (
<div className="b">
<h2>我是B组件</h2>
{/* <C></C> */}
{/* {this.props.children} */}
{/* 相当于Vue的插槽 */}
{this.props.aa(this.state.age)}
{this.props.render(this.state.name)}
</div>
)
}
}
class C extends Component {
render() {
return (
<div className="c">
<h2>我是C组件---接收B传递的值:{this.props.name}</h2>
</div>
)
}
}
class D extends Component {
render() {
return (
<div className="d">
<h2>我是D组件---接收B传递的值:{this.props.age}</h2>
</div>
)
}
}
8、错误边界
getDerivedStateFromError:衍生钩子。子组件报错触发并携带错误信息,需返回一个对象
componentDidCatch:渲染组件时出错触发
理解:用于控制错误的返回,不让它影响其它组件
作用:用来捕获后代组件的错误,渲染出备用页面
特点:只能捕获后代组件生命周期产生的错误,不能捕获自己组件产生的错误和其他组件在合成事件、定时器中产生的错误
使用场景:项目上线后,数据渲染出现问题,由getDerivedStateFromError捕获错误,在componentDidCatch进行统计反馈给服务器,通知编码人员进行bug修改
// 父
export default class A extends Component {
state = {
hasError: '' // 标识子组件是否产生错误
}
// 子组件出现错误时,会触发派生钩子调用,会携带错误信息
static getDerivedStateFromError(err) {
console.log('@err', err);
return { hasError: err }
}
// 组件渲染过程中,由子组件出现了问题触发下面钩子
componentDidCatch(error, info) {
// 统计页面的错误。发送请求发送到后台去
console.log('componentDidCatch',error, info);
}
render() {
return (
<div className='a'>
<h2>我是父组件</h2>
{this.state.hasError ? <h2>当前网络不稳定,稍后再试</h2> : <Child />}
</div>
)
}
}
// 子
export default class A extends Component {
// state = {name:'卡拉米'} // 特意注释代码,引发错误
render() {
return (
<div className='b'>
<h2>我是子组件-child--{this.state.name}</h2>
</div>
)
}
}
扩展
1、fetch
符合关注分离(Separation of Concerns)的原则。
关注分离理解(下面请求为例):请求数据后不在直接返回,细分为多个步骤
优点:
- 原生函数,不再使用XmlHttpRequest对象提交ajax请求,直接在浏览器中使用
- 也是promise风格
使用频率不高,因为不兼容低版本浏览器
fetch(url).then( // 这一步then只是确认是否连接服务器
response => {
console.log('联系服务器成功')
return response.json() // json():这个json是它身上的函数,返回priomse
},
error => {
console.log('联系服务器失败',error)
return new Promise(()=>{}) // 返回一个新的promise实例来中断promise
}
).then( // 这里才是获取数据
res => {
console.log('获取数据成功',res)
},
error => {
console.log('获取数据失败',error)
}
)
/* 【优化一】 */
fetch(url).then(
response => {
console.log('联系服务器成功')
return response.json()
}
).then( // 这里才是获取数据
res => {
console.log('获取数据成功',res)
}
).catch(error => { console.log('请求出错',error) })
/* 【优化二】 */
try{
const res = await fetch(url)
const data = await res.json()
console.log('获取数据成功',data)
} catch (err){
console.log('请求出错',err)
}