REACT基础知识一:
REACT是FACE-BOOK公司开发的一款MVC版JS框架
MVC:Model(数据层)、VIEW(视图层)、CONTROLLER(控制层)
核心思想:通过数据的改变来影响视图的渲染(数据驱动)
基于脚手架 CREATE-REACT-APP 快速构建一个REACT工程项目结构
- 自动安装REACT的核心组件:REACT/REACT-DOM
- 自动安装WEBPACK,并且完成相关的配置:
- 区分了开发环境和生产环境
- 安装BABEL以及对应的语言解析包,可以把REACT和ES6进行编译处理
- 安装CSS/STYLE/FILE等加载器,处理CSS等合并压缩的问题
- 安装了ES-LINT,可以进行代码检测
- 安装了很多的插件,可以实现JS和CSS以及HTML的分离,打包,压缩等
- 安装了WEBPACK-DEV-SERVER,可以在开发环境下,编译后自动创建服务,打开浏览器,当代码修改后,自动保存编译,页面自动刷新渲染等
使用脚手架
把脚手架安装到全局环境下,以后应用命令操作,完成项目结构的搭建
- $ npm install create-react-app -g
创建项目结构目录
- 项目名遵循NPM发包命名规范:名字只能是/^[a-z0-9_-]$/
- $ create-react-app 项目名
特点:如果当前电脑安装了YARN,创建工程目录的时候,走的是YARN安装,YARN和NPM主体相同,但是处理起来还有一定的区别,所以我们以后继续向工程中安装模块以及执行配置脚本打包编译的时候,尽可能使用YARN,不建议和NPM混用
工程化目录
|- node_modules
| |- .bin 所有在本地可执行的命令脚本(react-scripts.cmd)
| |- ...
|
|- package.json 当前项目的配置清单
|
|- public 存放的是当前项目的HTML页面(有可能放一部分静态资源)
| |- index.html
| |- ...
|
|- src 存放的是项目需要的所有JS或者静态资源等(包括组件、STORE、路由、数据模型、AJAX请求等等内容 “我们开发的内容基本上所有东西都在SRC中写”)
| |- index.js 当前项目的入口文件
| |- ...
暴露WEBPACK配置项
脚手架构建项目的时候,为了结构的美化,把所有的WEBPACK配置等都隐藏到了NODE_MODULES中(REACT-SCRIPTS中),真实项目中,我们经常会基于脚手架构建的结构自己在安装配置一些信息(例如:LESS处理的配置),此时我们需要把配置项暴露出来
如果当前的项目基于git管理,在执行eject的时候,如果还有没有提交到历史区的内容,需要先提交到历史区,然后在eject才可以,否则报错:This git repository has untracked files or uncommitted changes …
$ yarn eject
此操作是不可逆转的(而且操作之前需要把所有修改的文件提交到GIT仓库中)
|- config
| |- webpack.config.dev.js 开发环境下的WP配置
| |- webpack.config.prod.js 生产环境下的WP配置
| |- paths.js 基本配置项(包含项目的入口信息)
| |- ...
|
|- scripts
| |- start.js / build.js / test.js 当我们执行yarn start/build/test的时候,执行的就是这三个JS文件
可执行的本地脚本命令
-
$ yarn start
- 创建一个端口号为3000,协议为HTTP的WEB服务
- 按照webpack.config.dev.js把项目编译
- 打开浏览器,预览我们正在开发的项目
- 当项目文件修改的时候,自动重新编译,浏览器页面自动刷新,展示最新的效果
[WINDOWS]
$ set HTTPS = true && yarn start
$ set PORT = 1234 && yarn start
[MAC/LINUX]
$ HTTPS = true yarn start
$ PORT = 1234 yarn start
-
$ yarn build
- 生成一个build文件夹,存放最后打包的文件
- 基于webpack.config.prod.js,把项目进行编译打包
- 部署上线的时候,只需要把buid中的内容发布即可
基于脚手架配置LESS
安装LESS和对应的加载器
$ yarn add less less-loader
修改开发和生产环境下的WEBPACK配置项
[DEV:159~193行]
{
test: /\.(css|less)$/,
use: [
require.resolve('style-loader'),
{
loader: require.resolve('css-loader'),
...
},
...
{
loader: require.resolve('less-loader')
},
],
},
[PROD:169~212行]
{
test: /\.(css|less)$/,
loader: ExtractTextPlugin.extract(
Object.assign(
{
fallback: {
loader: require.resolve('style-loader'),
options: {
hmr: false,
},
},
use: [
{
loader: require.resolve('css-loader'),
...
},
...
{
loader: require.resolve('less-loader'),
}
],
},
extractTextPluginOptions
)
),
},
REACT基础知识二
REACT是基于独有的JSX语法实现视图(数据和HTML)渲染的
JSX语法
A:JSX语法的渲染使用的是ReactDOM.render
ReactDOM.render([JSX元素],[指定的容器],[回调函数:当我们把JSX放到指定容器内,触发执行的函数]);
B:JSX = JAVASCRIPT + XML(HTML)
1. 不推荐存放JSX的容器是BODY(写BODY会报错),一般都是使用我么自己创建的一个元素(例如:创建#ROOT的DIV容器等)
ReactDOM.render(<h2>珠峰培训</h2>, root);
2. 不允许出现两个“根”元素,如果需要绑定复杂的结构,最外层嵌套一个容器做为根元素即可
ReactDOM.render(<h2>珠峰培训</h2><h3>哈哈</h3>, root); =>错误的
ReactDOM.render(<div>
<h2>珠峰培训</h2>
<h3>哈哈</h3>
</div>, root); =>正确的
3. 把数据嵌入到JSX中(不是嵌入到元素的属性中,而是正常的内容中)
=> 可以嵌入变量或者直接的数据值
let name='xxx';
ReactDOM.render(<div>
<h2>{name}</h2>
<h3>{'哈哈'}</h3>
</div>, root);
=> 不能嵌入对象(代指:{}、/^$/、日期对象、函数、或者数组中的某一项是前面的也不行 [一维简单的数据是可以的])
ReactDOM.render(<div>
<h2>{{name:'xxx'}}</h2> NO
<h3>{new Date()}</h3> NO
<h3>{[12,23,34]}</h3> OK
<h4>{(() => {
return '呵呵'; OK:把自执行函数的结果嵌入进来
})()}</h4>
</div>, root);
=> 可以嵌入基本类型值(null/undefined/布尔值都是空元素,也就是不显示任何的内容)
=> 大括号中可以嵌入JS表达式(执行JS代码需要有返回结果的)
循环创建的JSX元素需要设置标识KEY,并且在当前循环的时候,这个KEY需要唯一;而使用MAP是因为它有返回值,返回的是替换后的数组;
ReactDOM.render(<ul>
{
data.map((item, index) => {
return <li key={index}>
{item.id} {item.title}
</li>;
})
}
</ul>, root);
使用三元运算符解决判断操作,(IF和SWITCH都不可以)
ReactDOM.render(<ul>
{name ? '哈哈' : '呵呵'}
</ul>, root);
4. 可以给JSX元素设置属性
=>属性值对应大括号中 对象、函数 都可以放(也可以放JS表达式)
=>STYLE属性值必须是对象(不能是字符串)
=>CLASS 用 CLASS-NAME 代替
=>...
REACT基础知识三:
1.JSX语法:JSX元素\REACT元素\虚拟DOM
REACT是如何把JSX元素转换为真实的DOM元素并且添加到页面中?
1. 基于BABEL/BABEL-LOADER/BABEL-PRESET-REACT-APP:把JSX语法编译为REACT.CREATE-ELEMENT这种模式
=>CREATE-ELEMENT中至少两个参数,没有上限
第一个:目前是当前元素标签的标签名(字符串)
第二个:属性(没有给元素设置属性则为NULL)
其它的:当前元素的所有子元素内容(只要子元素是HTML,就会变为新的CREATE-ELEMENT)
2. 执行CREATE-ELEMENT,把传递进来的参数最后处理成为一个对象
{
type:'标签名',
props:{
自己设置的那些属性对象(但是对于KEY和REF来说是要提取出来的),
children:存放自己子元素的(没有子元素就没有这个属性),如果有多个子元素,就以数组的形式存储信息
},
ref:null,
key:null
}
3. 把生成的对象交给RENDER进行处理:把对象编程DOM元素,插入到指定的容器中
两种声明组件方式
真实项目中使用REACT都是基于组件或者模块开发的
- 函数式创建组件
- 基于类创建组件
调取组件的时候:BABEL解析,传递给CREATE-ELEMENT的第一个参数TYPE不在是字符串标签名,而是一个函数(类),生成的对象中,TYPE也是一个函数;当RENDER渲染的时候会根据TYPE类型做不同的事情(如果是字符串就是创建新元素,如果是函数,就会把函数执行,把返回的JSX对象创建成为新的元素进行渲染),函数执行的时候会把解析出来的对象中的PROPS做为参数传递给组件(函数)
函数式组件声明的特点:
- 会把基于CREATE-ELEMENT解析出来的对象中的PROPS做为参数传递给组件(可以完成:多次调取组件传递不同的信息)
- 一旦组件调取成功,返回的JSX就会渲染到页面上,但是后期不通过重新调取组件或者获取DOM元素操作操作的方式,很难再把渲染好组件中的内容更改 =>函数是组件声明是“静态组件”
声明函数式组件
Vote.js
import React from 'react';
//=>函数式组件声明:创建一个函数,里面返回一个JSX
export default function Vote(props) {
//=>PROPS:调取组件的时候传递进来的属性信息(可能包含:className/style/id...还有可能有children等)
return <section className={'panel panel-default'}
style={{width: '50%', margin: '20px auto'}}>
<div className={'panel-heading'}>
<h3 className={'panel-title'}>
{props.title}
</h3>
</div>
<div className={'panel-body'}>
支持人数:<span>0</span>
<br/>
反对人数:<span>0</span>
<br/>
支持比率:<span>0%</span>
<br/>
<br/>
{/!*存放自己调取组件时候,额外扩展的标记*!/}
{props.children}
{/!*{
React.Children.map(props.children, item => {
return item;
})
}*!/}
</div>
<div className={'panel-footer'}>
<button className={'btn btn-success'}>支持</button>
<button className={'btn btn-danger'}>反对</button>
</div>
</section>;
}
引用组件
函数式调取组件 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Vote from './component/Vote';
/*
* 真实项目中使用REACT都是基于组件或者模块开发的
* 1. 函数式创建组件
* 2. 基于类创建组件
*
* 调取组件的时候:BABEL解析,传递给CREATE-ELEMENT的第一个参数TYPE不在是字符串标签名,而是一个函数(类),生成的对象中,TYPE也是一个函数;当RENDER渲染的时候会根据TYPE类型做不同的事情(如果是字符串就是创建新元素,如果是函数,就会把函数执行,把返回的JSX对象创建成为新的元素进行渲染),函数执行的时候会把解析出来的对象中的PROPS做为参数传递给组件(函数)
*
* 函数式组件声明的特点:
* 1. 会把基于CREATE-ELEMENT解析出来的对象中的PROPS做为参数传递给组件(可以完成:多次调取组件传递不同的信息)
* 2. 一旦组件调取成功,返回的JSX就会渲染到页面上,但是后期不通过重新调取组件或者获取DOM元素操作操作的方式,很难再把渲染好组件中的内容更改 =>函数是组件声明是“静态组件”
*/
ReactDOM.render(<div>
<Vote title={'德国对阵墨西哥,德国战车必胜!'}/>
<Vote title={'夏侯楙和曹冲两人谁帅?都很丑!'}>
{/*双闭合调取组件,就是为了传递子元素的*/}
<p>投票说明:上善若水。水善利万物而不争,处众人之所恶,故几于道。居,善地;心,善渊;与,善仁;言,善信;政,善治;事,善能;动,善时。夫唯不争,故无尤。</p>
</Vote>
</div>, root);
类创建组件(基于继承COMPONENT类实现的)
React中的受控组件和非受控组件的概念:
- 基于数据驱动(修改状态数据,react帮助我们重新渲染视图)完成的组件叫做“受控组件”(受数据控制的组件)
- 基于
ref
操作 DOM实现视图更新的,叫做“非受控组件”
数据驱动思想
import React from 'react';
// 1. 调取组件相当于创建类的实例(THIS),把一些私有的属性挂载到实例上了,这样组件内容所有方法中都可以基于实例获取这些值(包括:传递的属性和自己管理的状态)
// 2. 有自己的状态管理,当状态改变的时候,REACT会重新渲染视图(差异更新:基于DOM-DIFF只把需要重新渲染的部分渲染即可)
export default class Vote extends React.Component {
constructor(props) {
super(props); //=>React.Component.call(this) 可以把componengt中的私有属性继承过来 this.props / this.state(this.setState) / this.contect / this.refs / this.updater
//=>初始化状态
this.state = {
n: 0,
m: 0
};
}
render() {
let {title, children} = this.props,
{n, m} = this.state,
rate = (n / (n + m)) * 100;
isNaN(rate) ? rate = 0 : null;
return <section className={'panel panel-default'}
style={{width: '50%', margin: '20px auto'}}>
<div className={'panel-heading'}>
<h3 className={'panel-title'}>
{title}
</h3>
</div>
<div className={'panel-body'}>
支持人数:<span>{n}</span>
<br/>
反对人数:<span>{m}</span>
<br/>
支持比率:<span>{rate.toFixed(2) + '%'}</span>
<br/>
<br/>
{children}
</div>
<div className={'panel-footer'}>
<button className={'btn btn-success'}
onClick={this.support}>支持
</button>
<button className={'btn btn-danger'}
onClick={this.against}>反对
</button>
</div>
</section>;
}
support = ev => {
//=>使用箭头函数是为了保证方法中的THIS永远是实例本身(无论在哪执行这个方法)
//=>EV.TARGET:获取当前操作的事件源(DOM元素)
this.setState({
//=>修改状态信息并且通知RENDER重新渲染(异步操作:如果有其它代码执行,先执行其它的代码,然后再去通知状态修改)
n: this.state.n + 1
}, () => {
//=>回调函数一般不用:当通知状态更改完成,并且页面重新渲染完成后,执行回调
});
};
against = ev => {
this.setState({
m: this.state.m + 1
});
};
}
DOM操作思想
import React from 'react';
//=>REF是REACT中提供操作DOM的方案
//1. 给需要操作的元素设置REF(保持唯一性,否则会冲突覆盖)
//2. 在实例上挂载了REFS属性,它是一个对象,存储了所有设置REF的元素(REF值:元素对象)
export default class Vote extends React.Component {
constructor(props) {
super(props);
}
render() {
let {title, children} = this.props;
return <section className={'panel panel-default'}
style={{width: '50%', margin: '20px auto'}}>
<div className={'panel-heading'}>
<h3 className={'panel-title'}>
{title}
</h3>
</div>
<div className={'panel-body'}>
支持人数:<span ref={'AA'}>0</span>
<br/>
反对人数:<span ref={'BB'}>0</span>
<br/>
支持比率:<span ref={'RATE'}>0%</span>
<br/>
<br/>
{children}
</div>
<div className={'panel-footer'}>
<button className={'btn btn-success'}
onClick={this.support}>支持
</button>
<button className={'btn btn-danger'}
onClick={this.against}>反对
</button>
</div>
</section>;
}
support = ev => {
this.refs.AA.innerHTML++;
this.computed();
};
against = ev => {
this.refs.BB.innerHTML++;
this.computed();
};
computed = () => {
let {AA, BB, RATE} = this.refs,
n = parseFloat(AA.innerHTML),
m = parseFloat(BB.innerHTML),
ra = (n / (n + m)) * 100;
isNaN(ra) ? ra = 0 : null;
RATE.innerHTML = ra.toFixed(2) + '%';
};
}
引用组件
函数式调取组件 index.js
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Vote from './component/Vote';
ReactDOM.render(<div>
<Vote title={'德国对阵墨西哥,德国战车必胜!'}/>
<Vote title={'夏侯楙和曹冲两人谁帅?都很丑!'}>
{/*双闭合调取组件,就是为了传递子元素的*/}
<p>投票说明:上善若水。水善利万物而不争,处众人之所恶,故几于道。居,善地;心,善渊;与,善仁;言,善信;政,善治;事,善能;动,善时。夫唯不争,故无尤。</p>
</Vote>
</div>, root);
3.生命周期函数
-
调取组件
- constructor
- componentWillMount 第一次渲染之前
- render 渲染
- componentDidMount 第一次渲染之后
-
组件重新渲染:内部状态改变、传递给组件的属性改变
- 状态改变:
- shouldComponentUpdate
- 是否允许组件更新:返回TRUE是允许,返回FALSE则不再继续向下走
- componentWillUpdate
- 更新之前:和should一样,方法中通过this.state.xxx获取的还是更新前的状态信息,方法有两个参数:nextProps/nextState存储的是最新的属性和状态信息
- 状态改变:
-
render 更新步骤
componentDidUpdate 更新之后
属性改变:
-
componentWillReceiveProps ( nextProps / nextState )
- 接收最新属性之前,基于this.props.xxx获取的是原有的属性信息,nextProps存储的是最新传递的属性信息
- shouldComponentUpdate 是否允许组件更新
componentWillUpdate 更新之前
render 更新
componentDidUpdate 更新之后
-
组件销毁
- componentWillUnmount 组件销毁之前
组件的属性是只读的:只能调取组件时候传递进来,不能自己在组件内部修改(但是可以设置默认值和规则)
组件的状态是可读写的:状态改变会引发组件的重新更新(状态是基于setState改变)
组件实例上可以放一些信息:这些信息只是为了方便在组件内任意方法中获取和使用的
实例上挂载的REFS:就是用来操作DOM的
实例上挂载的context:是用来实现组件之间信息传递的
复合组件信息传递
1. 复合组件:父组件嵌套子组件
传递信息的方式
A:父组件需要把信息传递给子组件
“属性传递”:调取子组件的时候,把信息基于属性的方式传递给子组件(子组件PROPS中存储传递的信息);这种方式只能父组件把信息传递子组件,子组件无法直接的把信息传递给父组件,也就是属性传递信息是单向传递的;
export default class Vote extends React.Component {
render() {
let {title, count} = this.props;
return <section className={'panel panel-default'} style={{width: '50%', margin: '20px auto'}}>
<VoteHead title={title}/>
...
</section>;
}
}
“上下文传递”:父组件先把需要给后代元素(包括孙子元素)使用的信息都设置好(设置在上下文中),后代组件需要用到父组件中的信息,主动去父组件中调取使用即可
//=>父组件设置信息
import React from 'react';
import PropTypes from 'prop-types';
import VoteHead from "./VoteHead";
import VoteBody from "./VoteBody";
import VoteFooter from "./VoteFooter";
export default class Vote extends React.Component {
//=>设置默认属性
static defaultProps = {
title: '标题不知道,随便投吧',
count: {
n: 0,
m: 0
}
};
/*
* 在父组件中
* 需要安装:prop-types $ yarn add prop-types
*
* 1. 设置子组件上下文属性值类型
* static childContextTypes = {};
*
* 2. 获取子组件的上下文(设置子组件的上下文属性信息)
* getChildContext(){return {}}
*/
static childContextTypes = {
//=>设置子组件上下文属性值类型
n: PropTypes.number,
m: PropTypes.number
};
getChildContext() {
//->RETURN的是啥,相当于给子组件上下文设置成为啥
let {count: {n = 0, m = 0}} = this.props;
return {
n,
m
};
}
constructor(props) {
super(props);
}
render() {
let {title} = this.props;
return <section className={'panel panel-default'} style={{width: '50%', margin: '20px auto'}}>
<VoteHead title={title}/>
<VoteBody/>
<VoteFooter/>
</section>;
}
}
/*========================*/
//=>子组件主动获取需要的信息
import React from 'react';
import PropTypes from 'prop-types';
export default class VoteBody extends React.Component {
/*
* 3. 子组件中设置使用传递进来的上下文类型:设置哪个的类型,子组件上下文才有那个属性,不设置的是不允许使用的
* 指定上下文属性类型需要和父组件中指定的类型保持一致,否则报错
* 使用:this.content.xxx
*/
static contextTypes = {
//=>首先类型需要和设置时候类型一样,否则报错;并且你需要用啥,就写啥即可;
n: PropTypes.number,
m: PropTypes.number
};
constructor(props, context) {
super(props, context);
}
render() {
let {n, m} = this.context,
rate = (n / (n + m)) * 100;
isNaN(rate) ? rate = 0 : null;
return <div className={'panel-body'}>
支持人数:<span>{n}</span>
<br/>
反对人数:<span>{m}</span>
<br/>
支持比率:<span>{rate.toFixed(2) + '%'}</span>
</div>;
}
}
属性 VS 上下文
- 属性操作起来简单,子组件是被动接收传递的值(组件内的属性是只读的),只能父传子(子传父不行,父传孙也需要处理:父传子,子再传孙);
- 上下文操作起来相对复杂一些,子组件是主动获取信息使用的(子组件是可以修改获取到的上下文信息的,但是不会影响到父组件中的信息,其它组件不受影响),一旦父组件设置了上下文信息,它后代组件都可以直接拿来用,不需要一层层的传递;
B:其实子组件也能修改父组件中的信息(子传父)
利用回调函数机制:父组件把一个函数通过属性或者上下文的方式传递给子组件,子组件中只要把这个方法执行即可(也就是子组件中执行了父组件方法,还可以传递一些值过去),这样父组件在这个方法中,想把自己的信息改成啥就改成啥
父组件通过属性传递回调函数修改父组件
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
/*HEAD*/
class Head extends React.Component {
render() {
return <div className='panel-heading'>
<h3 className='panel-title'>
{/*子组件通过属性获取父组件传递的内容*/}
点击次数:{this.props.count}
</h3>
</div>;
}
}
/*BODY*/
class Body extends React.Component {
render() {
return <div className='panel-body'>
<button className='btn btn-success'
onClick={this.props.callBack}>点我啊!
</button>
</div>;
}
}
/*PANEL*/
class Panel extends React.Component {
constructor() {
super();
this.state = {n: 0};
}
fn = () => {
//=>修改PANEL的状态信息
this.setState({
n: this.state.n + 1
});
};
render() {
return <section className='panel panel-default' style={{width: '50%', margin: '20px auto'}}>
{/*父组件中在调取子组件的时候,把信息通过属性传递给子组件*/}
<Head count={this.state.n}/>
{/*父组件把自己的一个方法基于属性传递给子组件,目的是在子组件中执行这个方法*/}
<Body callBack={this.fn}/>
</section>;
}
}
ReactDOM.render(<Panel/>, root);
父组件设置上下文传递回调函数
import React from 'react';
import PropTypes from 'prop-types';
import VoteHead from "./VoteHead";
import VoteBody from "./VoteBody";
import VoteFooter from "./VoteFooter";
export default class Vote extends React.Component {
//=>PROPS
static defaultProps = {
title: '标题不知道,随便投吧',
count: {
n: 0,
m: 0
}
};
//=>CONTEXT
static childContextTypes = {
n: PropTypes.number,
m: PropTypes.number,
callBack: PropTypes.func //=>设置回调函数
};
getChildContext() {
//=>只要RENDER重新渲染,就会执行这个方法,重新更新父组件中的上下文信息;如果父组件上下文信息更改了,子组件在重新调取的时候,会使用最新的上下文信息;(RENDER=>CONTEXT=>子组件调取渲染)
let {n, m} = this.state;
return {
n,
m,
callBack: this.updateContext
};
}
updateContext = type => {
//=>TYPE:'SUPPORT'/'AGAINST'
if (type === 'support') {
this.setState({n: this.state.n + 1});
return;
}
this.setState({m: this.state.m + 1});
};
constructor(props) {
super(props);
//=>INIT STATE
let {count: {n = 0, m = 0}} = this.props;
this.state = {n, m};
}
render() {
let {title} = this.props;
return <section className={'panel panel-default'} style={{width: '50%', margin: '20px auto'}}>
<VoteHead title={title}/>
<VoteBody/>
<VoteFooter/>
</section>;
}
}
/*=====================*/
//=>子组件通过把传递进来的回调执行达到修改父组件信息
import React from 'react';
import PropTypes from "prop-types";
export default class VoteFooter extends React.Component {
static contextTypes = {
callBack: PropTypes.func
};
constructor(props, context) {
super(props, context);
}
render() {
let {callBack} = this.context;
return <div className={'panel-footer'}>
<button className={'btn btn-success'} onClick={() => {
callBack('support');
}}>支持
</button>
<button className={'btn btn-danger'} onClick={() => {
callBack('against');
}}>反对
</button>
</div>;
}
}
2. 平行组件:兄弟组件或者毫无关系的两个组件
方案一:让两个平行组件拥一个共同的父组件
父:Parent
子:A / B
父组件中有一些信息,父组件把一个方法传递给A,A中把方法执行(方法执行修改父组件信息值),父组件再把最新的信息传递给B即可,等价于A操作,影响了B
方案二:基于REDUX来进行状态管理,实现组件之间的信息传输(常用方案)
REDUX可以应用在任何的项目中(VUE/JQ/RERACT的都可以),REACT-REDUX才是专门给REACT项目提供的方案
Redux状态管理
redux基础流程
index.js(创建store容器)
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Vote from './component/Vote/Vote';
/*
* 创建容器:需要把REDUCER传递进来
* REDUCER作用:
* 1.记录了所有状态修改的信息(根据行为标识走不同的修改任务)
* 2.修改容器中的状态信息
*
* [参数]
* state:容器中原有的状态信息(如果第一次使用,没有原有状态,给一个初始默认值)
* action:dispatch任务派发的时候传递的行为对象(这个对象中必有一个TYPE属性,是操作的行为标识,REDUCER就是根据这个行为标识来识别该如何修改状态信息)
*/
import {createStore} from 'redux';
let reducer = (state = {n: 0, m: 0}, action) => {
switch (action.type) {
case 'VOTE_SUPPORT':
state = {...state, n: state.n + 1};
break;
case 'VOTE_AGAINST':
state = {...state, m: state.m + 1};
break;
}
return state;//=>只有把最新的STATE返回,原有的状态才会被修改
};
let store = createStore(reducer);
/*
* 创建的STORE中提供三个方法:
* dispatch:派发行为(传递一个对象,对象中有一个TYPE属性),通知REDUCER修改状态信息
* subscribe:事件池追加方法
* getState:获取最新管理的状态信息
*/
//=>RENDER
ReactDOM.render(<main>
<Vote title={'英格兰对战巴拿马,哈里凯恩必胜!'}
store={store}/>
</main>, root);
安装redux
$ yarn add redux react-redux
React工程化目录结构
src
|- index.js 主入口文件
|- component 组件
| |- Vote 组件文件夹
| |- Vote.js
| |- VoteHead.js
| |- VoteBody.js
| |- VoteFooter.js
|- store 状态管理文件夹
| |-reducer 存放每一个模块的 reducer
| | |- vote.js
| | |- personal.js
| | |- ...
| | |- index.js 把每一个模块的reducer最后合并成一个reducer
| |-action 存放每一个模块需要进行的派发任务(ActionCreator)
| | |- vote.js
| | |- personal.js
| | |- ...
| | |- index.js 所有模块的Action进行合并
| |-action-types.js 所有派发任务的行为标识都在这里进行宏观管理
| |-index.js 创建store
Redux:进行状态统一管理的类库(适用于任何技术体系的项目)
- 只要两个或者多个组件之间想要实现信息的共享,都可以基于redux共享,都可以基于redux解决,把共享的信息存储到redux容器中进行管理。
- 可以使用redux做临时存储,页面加载的时候,把从服务器获取的数据存储到redux中,组件渲染需要的数据,从redux中获取,这样只要页面不刷新,路由切换的时候,再次渲染组件不需要重新从服务器拉取数据,直接从redux中获取即可;页面刷新,从头开始!(这套方案代替了localStorage本地存储来实现数据缓存)
index.js(创建createStore)
import {createStore} from 'redux';
import reducer from './reducer';//<=>'./reducer/index'
let store = createStore(reducer);
export default store;
action-types.js(管控redux任务派发行为标识)
/*
* 管控当前项目中所有REDUX任务派发中需要的行为标识 ACTION-TYPE
*/
//=>VOTE
export const VOTE_SUPPORT = 'VOTE_SUPPORT';
export const VOTE_AGAINST = 'VOTE_AGAINST';
//=>PERSONAL
export const PERSONAL_INIT = 'PERSONAL_INIT';
reducer文件夹
vote.js
/*
* VOTE版块的REDUCER
* STATE:原始REDUX管理的状态信息(设置初始值)
* ACTION:DISPATCH派发的时候传递的行为对象 {TYPE,...}
*
* import * as TYPE from '../action-types' 把模块中所有导出的内容全部导入并重新命名为TYPE,此后TYPE对象中包含了所有导出的信息(ES6 Module)
*/
import * as TYPE from '../action-types';
export default function vote(state = {
n: 0,
m: 0
}, action) {
switch (action.type) {
case TYPE.VOTE_SUPPORT:
state = {...state, n: state.n + 1};
break;
case TYPE.VOTE_AGAINST:
state = {...state, m: state.m + 1};
break;
}
return state;
};
personal.js
import * as TYPE from '../action-types';
export default function personal(state = {
baseInfo: {}
}, action) {
//...
return state;
};
index.js(合并成为总的REDUCER)
/*
* 把每一个模块单独设定的REDUCER函数最后合并成为总的REDUCER
*
* 为了保证合并REDUCER过程中,每个模块管理的状态信息不会相互冲突,REDUX在合并的时候把容器中的状态进行分开管理(以合并REDUCER时候设置的属性名做为状态划分的属性名,把各个板块管理的状态放到自己的属性下即可)
* STATE={
* vote:{
* n:0,
* m:0
* },
* personal:{
* baseInfo:{}
* }
* }
*
* 以后获取状态信息的时候,也需要把VOTE等板块名加上了
* STORE.GET-STATE().VOTE.N
*/
import {combineReducers} from 'redux';
import vote from './vote';
import personal from './personal';
let reducer = combineReducers({
vote,
personal
});
export default reducer;
action文件夹
Vote.js
/*
* 每个板块单独的ACTION-CREATOR:就是把DISPATCH派发时候需要传递的ACTION对象进一步统一封装处理(REACT-REDUX中我们会体验到它的好处的)
*/
import * as TYPE from '../action-types';
let vote = {
support() {
//=>DISPATCH派发的时候需要传递啥就返回啥即可
return {
type: TYPE.VOTE_SUPPORT
};
},
against() {
return {
type: TYPE.VOTE_AGAINST
};
}
};
export default vote;
personal.js
import * as TYPE from '../action-types';
let personal = {};
export default personal;
index.js(合并)
/*
* 合并所有的ACTION-CREATOR,类似于REDUCER合并,为了防止冲突,合并后的对象是以板块名称单独划分管理
* ACTION={
* vote:{
* xxx(){}
* },
* ...
* }
*/
import vote from './vote';
import personal from './personal';
let action = {
vote,
personal
};
export default action;
主入口文件index.js使用(把store当做组件属性传过去)
import React from 'react';
import ReactDOM from 'react-dom';
import 'bootstrap/dist/css/bootstrap.css';
import Vote from './component/Vote/Vote';
import store from './store';
//=>RENDER
ReactDOM.render(<main>
<Vote title={'英格兰对战巴拿马,哈里凯恩必胜!'}
store={store}/>
</main>, root);
component文件夹
vote.js
import React from 'react';
import PropTypes from 'prop-types';
import VoteHead from "./VoteHead";
import VoteBody from "./VoteBody";
import VoteFooter from "./VoteFooter";
export default class Vote extends React.Component {
constructor(props) {
super(props);
}
render() {
let {store} = this.props;
return <section className={'panel panel-default'} style={{width: '50%', margin: '20px auto'}}>
<VoteHead title={this.props.title}/>
<VoteBody store={store}/>
<VoteFooter store={store}/>
</section>;
}
}
VoteBody.js(通过subscribe监听store状态改变从而执行事件池函数)
import React from 'react';
import PropTypes from 'prop-types';
export default class VoteBody extends React.Component {
constructor(props) {
super(props);
//=>INIT STATE
let {n, m} = this.props.store.getState().vote;
this.state = {n, m};
}
componentDidMount() {
this.props.store.subscribe(() => {
let {n, m} = this.props.store.getState().vote;
this.setState({n, m});
});
}
render() {
let {n, m} = this.state,
rate = (n / (n + m)) * 100;
isNaN(rate) ? rate = 0 : null;
return <div className={'panel-body'}>
支持人数:<span>{n}</span>
<br/>
反对人数:<span>{m}</span>
<br/>
支持比率:<span>{rate.toFixed(2) + '%'}</span>
</div>;
}
}
VoteFooter.js (通过dispatch派发改变reducer管理状态)
import React from 'react';
import PropTypes from "prop-types";
import action from '../../store/action'
export default class VoteFooter extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className={'panel-footer'}>
<button className={'btn btn-success'} onClick={() => {
this.props.store.dispatch(action.vote.support());
}}>支持
</button>
<button className={'btn btn-danger'} onClick={() => {
this.props.store.dispatch(action.vote.against());
}}>反对
</button>
</div>;
}
}
VoteHead.js
import React from 'react';
export default class VoteHead extends React.Component {
constructor(props) {
super(props);
}
render() {
return <div className={'panel-heading'}>
<h3 className={'panel-title'}>
{this.props.title}
</h3>
</div>;
}
}
React-redux 状态管理
React-redux:react-redux是把redux进一步封装,适配react项目,让redux操作更简洁
- store文件夹中的内容和redux一模一样
- 在组件调取使用的时候可以优化一些步骤
- Provider 根组件
- 当前整个项目都在Provider组件下
- 作用就是把创建的store可以供内部任何后组件使用(基于上下文完成的)
- 步骤:
- Provider组件中只允许出现一个子元素
- 把创建的store基于属性传递给Provider(这样后袋组件中都可以使用这个store了)
- connect 高阶组件
react-router-dom 实现SPA单页面应用
3 以前的版本成为:react-router
4 往后更新版本为:react-router-dom
$ yarn add react-router-dom
BorwserRouter VS HashRouter
它是两种常用的路由实现思想,BrowserRouter浏览器路由,HashRouter哈希路由
BrowserRouter
它是基于H5中history API (pushState,replaceState,popstate)来保持UI和URL的同步,真实项目中应用的不多,一般只有当前项目是基于服务器端渲染的,我们才会使用浏览器路由
http://www.demo.com/
http://www.demo.com/peraonal/
http://www.demo.com/peraonal/login/
HashRouter
真实项目中(前后端分离的项目:客户端渲染),我们经常使用的是哈希路由来完成的,它依据相同的页面地址,不同的哈希值,来规划当前页面中的哪一个组件呈现渲染,它基于原生JS构造了一套类似于history API机制,每一次路由切换都是基于history stack完成的!
http://www.demo.com/#/
http://www.demo.com/#/peraonal/
http://www.demo.com/#/peraonal/login/
**HashRouter路由基础使用 **
- 当前项目一旦使用HASH-ROUTER,则默认在页面的地址后面加上“#/”,也就是HASH默认值是一个斜杠,我们一般让其显示首页组件信息内容
- HASH-ROUTER中只能出现一个子元素
- HASH-ROUTER机制中,我们需要根据哈希地址不同,展示不同的组件内容,此时需要使用ROUTE
route
path
:设置匹配地址,但是默认不是严格匹配,当前页面哈希地址只要包含完整的它(内容是不变的),都能被匹配上
-
path = '/'
:和它匹配的地址只有要斜杠即可(都能和它匹配) -
path='/user'
:“#/user/login”也可以匹配,但是“#/user2”这个无法匹配
conponent
:一但哈希值和当前route的path相同了,则渲染COMPONENT指定的组件
exact
:让PATH的匹配严谨和严格一些(只有URL哈希值和PATH设定的值相等才可以匹配到)
-
path='/'
:“#/”匹配,但是“#/user”就不再匹配了
render:当页面的哈希地址和 path
匹配,会把 render
规划的方法执行,在方法中一般做“权限校验”(渲染组件之前验证是否存在权限,不存在做一些特殊处理)
HashRouter 路由规则
默认情况下,会和每一个 route
都做校验(哪怕之前已经有校验成功的),switch
组件可以解决这个问题,和 switch case
一样,只要有一种情况校验成功,就不在向后校验了
import React from 'react';
import ReactDOM, {render} from 'react-dom';
import {HashRouter, Route, Switch, Redirect} from 'react-router-dom';
import A from './component/A';
import B from './component/B';
import C from './component/C';
/*
* HashRouter
* 1. 当前项目一旦使用HASH-ROUTER,则默认在页面的地址后面加上“#/”,也就是HASH默认值是一个斜杠,我们一般让其显示首页组件信息内容
*
* 2. HASH-ROUTER中只能出现一个子元素
*
* 3. HASH-ROUTER机制中,我们需要根据哈希地址不同,展示不同的组件内容,此时需要使用ROUTE
*
* ROUTE
* PATH:设置匹配地址,但是默认不是严格匹配,当前页面哈希地址只要包含完整的它(内容是不变的),都能被匹配上
* PATH='/' :和它匹配的地址只有要斜杠即可(都能和它匹配)
* PATH='/user':“#/user/login”也可以匹配,但是“#/user2”这个无法匹配
*
* COMPONENT:一但哈希值和当前ROUTE的PATH相同了,则渲染COMPONENT指定的组件
*
* EXACT:让PATH的匹配严谨和严格一些(只有URL哈希值和PATH设定的值相等才可以匹配到)
* PATH='/':“#/”匹配,但是“#/user”就不再匹配了
*
* STRICT
*
* RENDER:当页面的哈希地址和PATH匹配,会把RENDER规划的方法执行,在方法中一般做“权限校验”(渲染组件之前验证是否存在权限,不存在做一些特殊处理)
*
* ----
* 默认情况下,会和每一个ROUTE都做校验(哪怕之前已经有校验成功的),SWITCH组件可以解决这个问题,和SWITCH CASE一样,只要有一种情况校验成功,就不在向后校验了
*/
render(<HashRouter>
<Switch>
<Route path='/' exact component={A}/>
<Route path='/user' component={B}/>
<Route path='/pay' render={() => {
let flag = localStorage.getItem('FLAG');
if (flag && flag === 'SAFE') {
return <C/>;
}
return '当前环境不安全,不利于支付';
}}/>
{/*上述都设置完成后,会在末尾设置一个匹配:以上都不符合的情况下,我们认为路由地址是非法的地址,我们做一些特殊处理(ROTUE不设置PATH是匹配所有地址规则的)*/}
{/*<Route render={() => {
return <div>404</div>;
}}/>*/}
{/*
* 也可以基于REDIRCT进行重定向
* TO [STRING]:重新定向到新的地址
* TO [OBJECT]:重新定向到新的地址,只不过指定了更多的信息
* {
* PATHNAME:定向的地址
* SEARCH:给定向的地址问号传参(结合当前案例,真实项目中,我们有时候会根据是否存在问号参数值来统计是正常进入首页还是非正常跳转过来的,也有可能根据问号传参值做不同的事情)
* STATE:给定向后的组件传递一些信息(后面会具体聊)
* }
*
* PUSH:如果设置了这个属性,当前跳转的地址会加入到HISTORY STACK中一条记录
* FROM:设定当前来源的页面地址
* <Redirect from='/custom' to='/custom/list'/>
* 如果当前请求的HASH地址是“/custom”,我们让其重定向到“/custom/list”
*/}
{/*<Redirect to='/?lx=404'/>*/}
<Redirect to={{
pathname: '/',
search: '?lx=404'
}}/>
</Switch>
</HashRouter>, root);
Link && NavLink && WithRouter
import React from 'react';
import {connect} from 'react-redux';
import {Link, NavLink, withRouter} from 'react-router-dom';
/*
* Link:是REACT-ROUTER中提供的路由切换组件,基于它可以实现点击时候路由的切换
* TO [STRING]:跳转到指定的路由地址
* TO [OBJECT]:可以提供一些参数配置项(和REDIRECT类似)
* {
* PATHNAME:跳转地址
* SERACH:问号传参
* STATE:基于这种方式传递信息
* }
* REPLACE:FALSE 是替换HISTORY STACK中当前的地址(TRUE),还是追加一个新的地址(FALSE)
*
* 原理:基于LINK组件渲染,渲染后的结果就是一个A标签,TO对应的信息最后变为HREF中的内容
* <a class="navbar-brand" href="#/?lx=logo">珠峰培训CRM</a>
*
* ------
*
* REACT-ROUTER中提供的组件都要在任何一个ROUTER(HASH-ROUTER)包裹的范围内使用
*
* ------
*
* NAV-LINK:和LINK类似,都是为了实现路由切换跳转的,不同在于,NAV-LINK组件在当前页面HASH和组件对应地址相吻合的时候,会默认给组件加一个ACTIVE样式,让其有选中态
* 和LINK类似,TO和REPLACE等属性都有,用法一样
*
* activeClassName:把默认加的active样式类改为自己设定的
* activeStyle:给匹配的这个NAV-LINK设置行内样式
* exact & strict控制匹配的时候是否是严格匹配
* isActive:匹配后执行对应的函数
*
* <NavLink to='/custom'>最后也会转换为A标签,如果当前页面的HASH地址和此组件中的TO地址匹配了,则会给渲染后的A标签设置默认的样式类:active
*/
/*
* WITH-ROUTER:这个方法意思是把一个非路由管控的组件,模拟成为路由管控的组件
* <ROUTE PATH='/' COMPONENT={NAV}/> 受路由管控的组件
*
* WITH-ROUTER(CONNECT()(NAV)) 先把NAV基于CONNECT高阶一下,返回的是一个代理组件PROXY,把返回的代理组件受路由管控
*
* 受路由管控组件的一些特点:
* 1. 只有当前页面的哈希地址(/#/...)和路由指定的地址(PATH)匹配,才会把对应的组件渲染(WITH-ROUTER是没有地址匹配,都被模拟成为受路由管控的)
*
* 2. 路由切换的原理:凡是匹配的路由,都会把对应的组件内容,重新添加到页面中,相反,不匹配的都会在页面中移除掉,下一次重新匹配上,组件需要重新渲染到页面中;每一次路由切换的时候(页面的哈希路由地址改变),都会从一级路由开始重新校验一遍;
*
* 3. 所有受路由管控的组件,在组件的属性PROPS上都默认添加了三个属性:
* HISTORY
* PUSH 向池子中追加一条新的信息,达到切换到指定路由地址的目的
* this.props.history.push('/plan') JS中实现路由切换
* GO 跳转到指定的地址(传的是数字 0当前 -1上一个 -2上两个...)
* GO-BACK <=> GO(-1) 回退到上一个地址
* GO-FORWARD <=> GO(1) 向前走一步
* ...
*
* LOCATION 获取当前哈希路由渲染组件的一些信息
* PATHNAME:当前哈希路由地址 /custom/list
* SEARCH:当前页面的问号传参值 ?lx=unsafe
* STATE:基于REDIRECT/LINK/NAV-LINK中的TO,传递是一个对象,对象中编写的STATE,就可以在LOCATION.STATE中获取到
*
* MATCH 获取的是当前路由匹配的一些结果
* PARAMS:如果当前路由匹配的是地址路径参数,则这里可以获取传递参数的值
*/
class Nav extends React.Component {
constructor(props, context) {
super(props, context);
console.log(props);
}
render() {
return <nav className='navbar navbar-default'>
{/*LOGO*/}
<div className='container-fluid col-md-2'>
<Link className='navbar-brand' to={{
pathname: '/',
search: '?lx=logo'
}}>珠峰培训CRM</Link>
</div>
{/*NAV*/}
<div className='collapse navbar-collapse col-md-10'>
<ul className='nav navbar-nav'>
{/*NAV-LINK不是点击谁,谁有选中的样式(但是可以路由切换),而且当前页面哈希后的地址和NAV-LINK中的TO进行比较,哪个匹配了,哪个才有选中的样式*/}
<li><NavLink to='/' exact>首页</NavLink></li>
<li><NavLink to='/custom'>客户管理</NavLink></li>
<li><NavLink to='/plan'>计划管理</NavLink></li>
</ul>
</div>
</nav>;
}
}
export default withRouter(connect()(Nav));