目录
8.3 使用react脚手架创建一个hello world项目
11.12 BrowserRouter和HashRouter的对比
1.hello react
1.react需要使用三个依赖,必须按照先后顺序依次引入
2.resct使用的是jsx语言,再script标签的type属性上要写明“text/babel”
<body>
<!-- 创建容器 -->
<div id="app"></div>
<!-- 引入react核心库,全局就有了React对象 -->
<script src="./js/react.development.js"></script>
<!-- 引入react-dom,支持react的dom操作,全局就有了ReactDOM -->
<script src="./js/react-dom.development.js"></script>
<!-- 引入babel,将jsx转换为js -->
<script src="./js/babel.min.js"></script>
<script type="text/babel">
// 创建虚拟dom,此处不要加引号
const VDOM = <h1>hello react</h1>;
// 渲染虚拟dom到页面
ReactDOM.render(VDOM,document.getElementById("app"));
</script>
</body>
react虚拟dom的另一种创建方式:但是这种方式对于层级较深的html结构是非常复杂的,一般使用方法一,且方法一在运行的时候也是转换成方法二的。
<body>
<!-- 创建容器 -->
<div id="app"></div>
<script type="text/javascript">
const VDOM = React.createElement('h1',(id:'title'),'hello react');
ReactDOM.render(VDOM,document.getElementById('test'));
</script>
</body>
2.jsx语法规则
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title>Document</title>
<style>
.title{
background-color: coral;
}
#abcdef{
border: 1px solid black;
}
</style>
</head>
<body>
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
const myId = 'abCdeF';
const myData = 'hello,rEaCt';
// 多行html虚拟dom写法---外面要加括号
const VDOM = (
<div>
<!--js表达式要用{}包裹 -->
<h2 className="title" id={myId.toLowerCase()}>
<span style={{color:'white',fontSize:'29px'}}>{myData.toLowerCase()}</span>
</h2>
<!-- 必须要有结束标记或标签-->
<input type="text"/>
</div>
);
ReactDOM.render(VDOM,document.getElementById("test"));
</script>
</body>
3.组件
3.1 创建函数式组件
执行过程:
1.React解析组件标签,找到MyComponent组件;
2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟dom转换为真实dom,随后呈现在页面中;
<body>
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
// 创建函数式组件
function MyComponent(){
console.log(this); // undefined
return <h1>这是一个自定义react组件</h1>;
}
ReactDOM.render(<MyComponent/>,document.getElementById("test"));
</script>
</body>
在函数组件中的this是undefined,因为babel编译后开启了严格模式
注意点:
3.2 类式组件的创建
3.2.1 类的基本知识
1.类中的构造器不是必须写的,要对实例进行一些初始化操作,如添加指定属性时候才写;
2.如果A类继承了B类,且A类中写了构造器,那么A类的构造器是必须调用的;
3.类中定义的方法,都是放在类的原型对象上,供实例使用;
<script>
class person{
constructor(name,age){
this.name = name;
this.age = age;
}
speak(){
console.log(this.name,this,age);
}
}
class student extends person{
constructor(name,age,grade){
super(name,age);
this.grade = grade;
}
speak(){
console.log(this.name,this.grade);
}
study(){
console.log("我学习!");
}
}
const s = new student("zs",12,90);
console.log(s);
</script>
3.2.2 类式组件
执行了ReactDOM.render()后:
1.react解析组件标签,找到myComponent组件
2.发现组件是使用类定义的,new出该类的实例,通过该实例调用原型上的render方法;
3.将render返回的虚拟dom转换为真实dom,呈现于页面;
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class MyComponent extends React.Component {
render(){
return <h1>自定义类组件</h1>;
}
}
ReactDOM.render(<MyComponent/>,document.getElementById('test'));
</script>
render(){}是放在myComponent的原型对象上,供实例使用;
render(){}中的this是myComponent的实例对象。·
3.2.3 组件实例的三大核心属性
3.2.3.1 state属性
点击文字,每次点击切换当前天气的状态,即炎热凉爽之间切换
class Weather extends React.Component {
// 构造器在整个过程中调用一次
constructor(props){
super(props);
// 初始化状态
this.state = {isHot:false};
// 解决changeWeather中this指向问题
this.changeWeather = this.changeWeather.bind(this);
}
// render调用1 + n次,初始化调用一次,以及状态更新时调用
render(){
const {isHot} = this.state;
// this.changeWeather不能加括号,加了括号就是将这个函数的返回值给onClick事件作为回调,不加括号则意味着是将整个函 数作为回调
return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
}
// 点几次调用几次
changeWeather(){
// changeWeather放在Weather原型对象上,供实例使用
// changeWeather 是作为onClick的回调,不是通过实例调用的,是直接调用
// 类中的方法默认开启了局部严格模式,所以changWeather中的this是undefined
const {isHot} = this.state;
console.log(isHot);
// 状态不能直接修改,要通过setState进行修改
this.setState({isHot:!isHot});
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));
简写形式:
<script type="text/babel">
class Weather extends React.Component {
// 初始化状态
state = {isHot:false};
render(){
const {isHot} = this.state;
return <h2 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'}</h2>
}
// 自定义函数 赋值语句 + 箭头函数
changeWeather = () => {
const {isHot} = this.state;
this.setState({isHot:!isHot});
}
}
ReactDOM.render(<Weather/>,document.getElementById('test'));
</script>
state是组件对象最重要的属性,值是对象,可以包括多个key-value组合;
组件被称为“状态机”,通过更新组件的state来更新对应页面的显示,重新渲染组件;
组件中render方法中的this为组件实例对象;
自定义方法中this为undefined,可以强制绑定this,通过函数对象的bind();箭头函数。
状态数据不能直接修改或更新
3.2.3.2 props属性
组件标签所有属性都保存在props中;
通过标签属性从组件外向内传递变化的数据;
组件内部不能修改props数据;
标签可以直接写属性,也可以通过构造对象字面量的方式;
可以对传递的参数进行限制,需要进行单独配置(类名.propTypes);其中对于属性的配置在react15.5之前可以使用React.PropTypes,在15.5之后需要引入prop-types.js,相当于在全局中引入了PropTypes对象;
在标签中需要传入数值型的属性时,数值外面要加{};
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script src="./js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
render(){
const {name,age,sex} = this.props;
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
}
// 对标签属性进行类型,必要性的限制
Person.propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, // 设置sex为字符串
age:PropTypes.number, // 设置age为数值
speak:PropTypes.func // 设置speak为函数,注意写法func
}
// 标签属性默认值
Person.defaultProps = {
sex:'male',
age:18
}
ReactDOM.render(<Person name="zs" age={18} sex="male" speak={speak}/>,
document.getElementById('test1'));
const p = {name:'ls',age:19,sex:'男'};
// 批量传递props
// 构造对象字面量时展开运算符不能展开对象即(...对象名),但可以{...对象名}
// 在react中babel + react可以展开一个对象,但仅适用于标签属性的传递
// {}包裹在react表达式外面,其实写法是...p
ReactDOM.render(<Person {...p}/>,document.getElementById('test2'));
function speak(){
console.log('speak');
}
</script>
props的简写形式,将propTypes作为类的静态属性
<div id="test1"></div>
<div id="test2"></div>
<div id="test3"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script src="./js/prop-types.js"></script>
<script type="text/babel">
class Person extends React.Component {
constructor(props){
// 构造器是否接收props,是否传递给super,取决于是否希望在构造函数中通过this访问props
super(props);
console.log(this.props);
}
render(){
const {name,age,sex} = this.props;
return(
<ul>
<li>姓名:{name}</li>
<li>性别:{sex}</li>
<li>年龄:{age+1}</li>
</ul>
)
}
// 对标签属性进行类型,必要性的限制
static propTypes = {
name:PropTypes.string.isRequired, //限制name必传,且为字符串
sex:PropTypes.string, // 设置sex为字符串
age:PropTypes.number, // 设置age为数值
speak:PropTypes.func
}
// 标签属性默认值
static defaultProps = {
sex:'male',
age:18
}
state = {}
}
ReactDOM.render(<Person name="zs" sex="male" speak={speak}/>,document.getElementById('test1'));
const p = {name:'ls',age:19,sex:'男'};
// 批量传递props
// 构造对象字面量时展开运算符不能展开对象即(...对象名),但可以{...对象名}
// 在react中babel + react可以展开一个对象,但仅适用于标签属性的传递
// {}包裹在react表达式外面,其实写法是...p
ReactDOM.render(<Person {...p}/>,document.getElementById('test2'));
function speak(){
console.log('speak');
}
</script>
3.2.3.3 refs属性
组件内的标签可以通过refs来标识自己,相当于id选择器
1.字符串形式的ref(不推荐使用)
在标签中使用ref打标识ref="标识名",react会将所有收集到的标识放入组件的refs中,以key(标识名):value(标签真实dom)的形式
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
showData1 = () => {
const {input1} = this.refs;
alert(input1.value);
}
showData2 = () => {
const {input2} = this.refs;
alert(input2.value);
}
render(){
return(
<div>
<input type="text" ref="input1" placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
<input type="text" ref="input2" onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
<script type="text/babel">
ref写成**内联函数**的问题:在页面更新过程中会调用两次,第一次会传入参数c为null,第二次才会传入dom元素。
由于每次渲染时会创建一个新的函数实例,所以react会先清空旧的再传入新的;
可以通过ref的回调定义为**类的绑定函数**的方式避免;(见上述代码24行);但影响不大,实际还是多用内联函数。
3. creatRef的使用
React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点,该容器是专人专用的
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
myRef1 = React.createRef();
myRef2 = React.createRef();
showData1 = () => {
console.log(this.myRef1); // {current: input}
alert(this.myRef1.current.value);
}
showData2 = () => {
alert(this.myRef2.current.value);
}
render(){
return(
<div>
<input type="text" ref={this.myRef1} placeholder="点击按钮提示数据"/>
<button onClick={this.showData1}>点我提示左侧数据</button>
<input type="text" ref={this.myRef2} onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>
4. 事件处理
1.通过onXxx属性指定事件处理函数
React使用的是自定义事件,而不是dom原生事件 --- 为了更好的兼容性
React中的事件是通过事件委托的方式处理的,委托给最外层的元素 --- 为了高效
2.通过event.target得到发生事件的dom元素对象 ,所以不要过度使用ref,当发生事件的元素是要操作的元素时,可以省略ref,在事件的回调中通过event.target获取,代码如下:
<div id="test"></div>
<script src="./js/react.development.js"></script>
<script src="./js/react-dom.development.js"></script>
<script src="./js/babel.min.js"></script>
<script type="text/babel">
class Demo extends React.Component {
showData2 = (event) => {
alert(event.target.value);
}
render(){
return(
<div>
<input type="text" onBlur={this.showData2} placeholder="失去焦点提示数据"/>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>
5.受控组件
页面中的输入类型节点随着输入,将输入内容维护到状态state中去,需要使用的时候从状态state中取出。
能够省略调ref;
类似于vue中的双向数据绑定。
<script type="text/babel">
class Form extends React.Component{
// 状态初始化
state = {
username:'',
password:''
}
handleName = (event) => {
// 拿到输入的内容,放入state中保存
this.setState({username:event.target.value});
}
handlePwd = (event) => {
this.setState({password:event.target.value});
}
handleSubmit = (event) => {
event.preventDefault();
// 从state中取出输入的数据
alert(`输入的用户名为${this.state.username},密码为${this.state.password}`)
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" name="username" onChange={this.handleName}/>
密码:<input type="password" name="pwd" onChange={this.handlePwd}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>
6.非受控组件
页面中的输入节点,将输入的内容直接取出使用。
<script type="text/babel">
class Form extends React.Component{
handleSubmit = (event) => {
event.preventDefault(); // 阻止表单提交
const {input1,input2} = this;
alert(`输入的用户名为${input1.value},密码为${input2.value}`);
}
render(){
return(
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" ref={c => this.input1 = c} name="username"/>
密码:<input type="password" ref={c => this.input2 = c} name="pwd"/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>
6.1对上述代码的优化
1.高阶函数:如果一个函数满足下面两个规范中的任何一个规范,那么该函数就是高阶函数
1.1 若A函数,接收的参数是一个函数
1.2 若A函数,调用的返回值依然是一个函数
常见的高阶函数有:Promise,setTimeout,arr.map()等
2. 函数的柯里化:通过函数调用继续返回函数的形式,实现多次接收参数最后统一处理的函数编码形式
add(a){
return (b) => {
return (c) => {
return a + b + c;
}
}
}
add(1)(2)(3);
代码优化:
this.handleChange()返回给onChange事件的是该方法的返回值,所以给handleChange()方法的返回一个回调函数,给onChange接收,即当表单数据发生改变的时候,就执行该回调,该回调的参数就是当前表单的事件对象event,通过这种函数柯里化的形式,解决给事件回调传递参数.
<script type="text/babel">
class Form extends React.Component {
state = {
username:'',
password:''
}
handleChange = (type) => {
return (event) => {
this.setState({[type]:event.target.value});
}
}
handleSubmit = () => {
const {username,password} = this.state;
alert(`${username}${password}`);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" name="username" onChange={this.handleChange('username')}/>
密码:<input type="password" name="pwd" onChange={this.handleChange('password')}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>
不使用柯里化解决:
直接传递给onChange事件一个回调,在这个回调种返回handleChange函数,将两个参数传入:
<script type="text/babel">
class Form extends React.Component {
state = {
username:'',
password:''
}
handleChange = (type,event) => {
this.setState({[type]:event.target.value});
}
handleSubmit = () => {
const {username,password} = this.state;
alert(`${username}${password}`);
}
render(){
return (
<form onSubmit={this.handleSubmit}>
姓名:<input type="text" onChange={event => this.handleChange('username',event)}/>
密码:<input type="password" onChange={event => this.handleChange('password',event)}/>
<button>提交</button>
</form>
)
}
}
ReactDOM.render(<Form/>,document.getElementById('test'));
</script>
7. 组件的生命周期
组件从创建到死亡要经历一些特定的阶段;React组件中包含一系列钩子函数,会在特定的时刻调用;我们在定义组件时,会在特定的生命周期函数中,做特定的工作。
7.1生命周期(旧)
7.1.1组件初始化流程
class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}
// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}
// 组件将要挂载的钩子
componentWillMount(){
console.log("componentWillMount");
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log("componentDidMount")
}
render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));
7.1.2组件更新流程
class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}
// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}
// 控制组件更新的阀门,默认不写react会自动调用并返回true;
// 返回true则允许更新组件,返回false不更新组件即不调用render()
// 如果写了不return,默认return的是undefined(false)
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>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));
点击+1按钮控制台输出如下:
7.1.3组件强制更新
对状态不做任何修改而更新组件,不受阀门shouldComponentUpdate影响
class Count extends React.Component {
// 构造器
constructor(props){
super(props);
this.state = {count:0}
}
// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}
// 强制更新组件
forceUp = () => {
this.forceUpdate();
}
// 控制组件更新的阀门
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.forceUp}>点击强制更新</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));
点击强制更新按钮,控制台输出如下:
7.1.4卸载组件
class Count extends React.Component {
// 卸载组件
deleted = () => {
ReactDOM.unmountComponentAtNode(document.getElementById("test"));
}
// 组件卸载钩子
componentWillUnmount(){
console.log("componentWillUnmount");
}
render(){
console.log("render");
return (
<div>
<h2>当前求和为8</h2>
<button onClick={this.deleted}>点击卸载</button>
</div>
)
}
}
ReactDOM.render(<Count/>,document.getElementById('test'));
7.1.5 父组件render流程
A组件为父组件,B组件为子组件,当组件第二次起获取父组件传递的参数时,调用componentWillReceiveProps钩子
class A extends React.Component{
state = {car1:'奔驰'}
changeCar = () => {
this.setState({car1:'宝马'})
}
render(){
return (
<div>
<h2>这是A组件</h2>
<button onClick={this.changeCar}>切换车</button>
<B car={this.state.car1}/>
</div>
)
}
}
class B extends React.Component{
// 组件将要接收函新的props的钩子,第一次传递props的时候不算
componentWillReceiveProps(props){
console.log("componentWillReceiveProps",props);
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}
// 组件将要更新的钩子
componentWillUpdate(){
console.log("componentWillUpdate");
}
// 组件更新完毕的钩子
componentDidUpdate(){
console.log("componentDidUpdate");
}
// 更新
render(){
console.log("render");
return (
<h2>这是子组件B,接收到A传递的参数{this.props.car}</h2>
)
}
}
ReactDOM.render(<A/>,document.getElementById('test'));
点击切换按钮,控制台输出如下:
7.2生命周期(新)
class Count extends React.Component {
// 构造器
constructor(props){
console.log("constructor");
super(props);
this.state = {count:0}
}
// 加1按钮回调
add = () => {
const {count} = this.state;
this.setState({
count:count + 1
})
}
// 强制更新组件
forceUp = () => {
this.forceUpdate();
}
// state的值在任何时候都要取决于props的时候使用,这里会将state的值改为return的props,即count:9
// 该钩子必须以static修饰,且返回值必须是null或者props对象
static getDerivedStateFromProps(props,state){
console.log("getDerivedStateFromProps",props,state);
//return props;
return null;
}
// 在组件更新前获取信息,把更新前的部分信息记录下来,比如某个div的高度
// 必须返回null或者快照信息
getSnapshotBeforeUpdate(){
console.log("getSnapshotBeforeUpdate");
return "2021102";
}
// 组件挂载完毕的钩子
componentDidMount(){
console.log("componentDidMount")
}
// 控制组件更新的阀门
shouldComponentUpdate(){
console.log("shouldComponentUpdate");
return true;
}
// 组件更新完毕的钩子
// 可以接收三个参数,更新之前的props,更新之前的状态,更新前getSnapshotBeforeUpdate返回的快照信息
componentDidUpdate(preProps,preState,snapshotValue){
console.log("componentDidUpdate",preProps,preState,snapshotValue);
}
// 组件卸载钩子
componentWillUnmount(){
console.log("componentWillUnmount");
}
render(){
console.log("render");
const {count} = this.state;
return (
<div>
<h2>当前求和为{count}</h2>
<button onClick={this.add}>点我 + 1</button>
<button onClick={this.forceUp}>点击强制更新</button>
<button onClick={this.deleted}>点击卸载</button>
</div>
)
}
}
ReactDOM.render(<Count count="9"/>,document.getElementById('test'));
```
将26行改为return null,点击点我+1,控制台输出如下:
8.React脚手架
用来帮助程序员快捷创建一个基于xxx库的模板项目
项目的整体技术架构为:react+webpack+es6+eslint
react提供了一个用于创建react项目的脚手架库create-react-app
8.1创建项目并启动
8.2react脚手架项目结构
8.3 使用react脚手架创建一个hello world项目
1. 首先在项目根目录下创建public和src两个文件
2.在public文件下创建index.html,作为项目的主页面;在src目录下创建App.js作为项目的根组件,index.js作为项目的入口js
3.在index.html中创建主页面的根节点
4.在入口index.js中分别引入react react-dom App.js,并将App组件渲染到页面
5.创建外壳组件App.js,使用分别暴露和统一暴露方式引入React和Component,创建App类组件;
将App组件暴露给外部export default
外壳组件中的内容一般使用组件的方式编写,这里还需再创建Hello Welcome两个组件
6. 在src目录下新建Component文件,用于存放自定义组件,在其下新建Hello,Welcome两个文件,存放两个组件的内容,组件的文件名的首字母习惯上用大写字母。此外为了区分组件和普通业务功能的js文件,我们习惯上将组件文件的后缀名命名为jsx,以作区分。css文件以css为后缀名。
7.分别创建两个组件,工程同App.js,最后在App.js中引入俩个组件,在render中添加两个组件标签(见第5步)。
8.按ctrl+`打开控制台,切换到项目当前目录,npm start启动项目,可以查看到最终效果。
8.4 样式的模块化
在上述案例中,welcome和Hello两个组件的最终内容都会汇总到App中,从而两个组件样式存在冲突,即出现在两个组件中的同名的类选择器会产生冲突,后引入的组件样式会覆盖先引入的。
1.将需要模块话的css文件名写成:文件名.module.css的格式
2.在组件中的css的import语句中给引入的css模块命名,在响应的类选择器处用css模块名.选择器名的表达式替代
8.5 react脚手架中配置代理
开发阶段,前端代码请求服务器,若两者端口号等不一致,则受同源策略保护,请求响应时会被拦截。
要在本地新建一个模拟的服务器(端口号相同),代替我们将请求转发给服务器,那么由于服务器之间的请求转发不存在同源策略限制,不会存在跨域问题。
8.5.1在package.json中配置
"proxy":"http://loaclhost:5000"
优点:配置简单,前端请求资源时候可以不添加任何前缀
缺点:不可配置多个代理
工作方式:当请求了3000不存在的资源时,将请求转发给5000
8.5.2 创建代理配置文件
1.创建创建代理配置文件
在src目录下创建配置文件:src/setupProxy.js
2.编写setupProxy配置代理具体规则:
9 fetch
原生函数,不使用XMLHttpRequest对象提交Http请求,Promise风格;
对于老版本的浏览器不支持
fetch(`http://localhost:3000/search/users?q=${input}`).then(
res => {
// 联系服务器成功了。要获取数据得调用json方法,它也是一个Promise
retrun res.json();
},
err => {
// 联系服务器失败了
console.log(err);
// 中断Promise链。由于默认返回值是undefined,依旧会走下一个then的成功回调。
return new Promise(() => {});
}
).then(
res => {
console.log(‘获取数据成功了’,res);
},
err => {
console.log(“获取数据失败了”,err);
}
)
用async/await:
async demo1(){
try{
const result = await fetch(`http://localhost:3000/search/users?q=${input}`);
const {items} = await result.json();
Pubsub.publish('getSearchData',{users:newArr,isLoading:false});
} catch(err){
Pubsub.publish("getSearchData",{isLoading:false,isError:true});
}
}
10 消息订阅与发布
工具库:Pubsub.js
解决兄弟组件之间转递参数的问题
下载安装:
使用:
1.订阅消息,写在接收数据的组件里
2. 发布消息,写在传送数据的组件里
3. 取消订阅,写在接收数据的组件中的componentWillUnmount钩子中
11. react路由
11.1 路由的基本使用
1. 明确好界面中的导航区域和展示区域
2. 导航区域的a标签改为Link标签:
3.展示区写Route标签进行路径的匹配
4.<App>的最外侧包裹一个<BrowserRouter>或<HashRouter>
import React, { Component } from 'react'
import {Link,Route} from 'react-router-dom'
import About from './Components/About'
import Home from './Components/Home'
export default class App extends Component {
render() {
return (
<div>
<div className="row">
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header"><h2>React Router Demo</h2></div>
</div>
</div>
<div className="row">
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 编写路由链接 */}
<Link className="list-group-item " to="/about">About</Link>
<Link className="list-group-item" to="/home">Home</Link>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</div>
</div>
</div>
</div>
</div>
)
}
}
index.js
// 引入react核心库
import React from 'react'
// 引入reactDOM
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
// 引入App组件
import App from './App'
// 渲染App组件到页面
ReactDOM.render(
<BrowserRouter>
<App/>
</BrowserRouter>,
document.getElementById('root')
);
效果:
11.2 路由组件和一般组件
1. 路由组件一般放在pages文件夹下;一般组件放在components文件夹下。
2. 写法不同:
1. ·路由组件:<Route path='/demo' component={Demo}>
2. 一般组件:<Demo/>
3. 接收到的props不同:
1. 路由组件:能接收到三个固定的属性
history:
location:
match:
2. 一般组件:组件标签传递什么就接收什么;
11.3 NavLink的使用
能实现点击组件标签呈现高亮效果;
高亮效果的样式通过activeClassName属性指定类选择器
import {NavLink} from 'react-router-dom'
import './App.css'
......
<div className="list-group">
{/* 编写路由链接 */}
<NavLink className="list-group-item " activeClassName="acn" to="/about">About</NavLink>
<NavLink className="list-group-item" activeClassName="acn" to="/home">Home</NavLink>
</div>
封装NavLink:
多个标签都要呈现同一种选中效果,避免重复的代码出现
App.js文件:
import MyNavLink from './Components/MyNavLink'
import './App.css'
<div className="list-group">
{/* 编写路由链接 */}
<MyNavLink to="/about" children="about"/>
<MyNavLink to="/home" children="home"/>
</div>
MyNavLink.jsx文件:
import React, { Component } from 'react'
import {NavLink} from 'react-router-dom'
export default class index extends Component {
render() {
return (
<div>
<NavLink className="list-group-item " activeClassName="acn" {...this.props}/>
</div>
)
}
}
children属性用来传递标签体,可以在组件内部**使用this.props.children获取传递的组件标签体内容**,所以自定义组件标签MyNavLink可以写成自结束标签,在MyNavLink组件内部**使用对象的结构赋值的方式{...this.props}获取标签属性**,所以以下两者是等价的:
<MyNavLink to="/about" children="about"/> <=> <MyNavLink to="/about">about</MyNavLink>
11.4 switch标签
路由一般一个路径对应一个组件,但是默认一个路径path依旧还会再往后的代码去找是否有与其对应的组件component属性,这样的操作是多余的,为了不让react做这样的匹配,只需在路由链接标签外加上<Switch>标签即可。
import {Switch,Route} from 'react-router-dom'
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
</Switch>
</div>
11.5 解决多级路径页面刷新样式丢失
1. public/index.html中引入样式不写./写/
./代表的是相对路径从当前路径下开始匹配
/代表从项目根路径开始匹配
2. public/index.html中引入样式时不写./写%PUBLIC_URL%
%PUBLIC_URL%代表public文件的绝对路径
3. 使用HashRouter
HashRouter将#放在项目根路径后面,#后面的路径一律不提交服务器,视作前端资源。
11.6 严格匹配
默认开启的是模糊匹配,即输入的路径必须包含匹配的路径。
保证路由链接和注册路由的路径是一致时才会触发路由,一般不开启,除非路径匹配出现问题。有时开启会导致无法继续匹配二级路由。
<div className="col-xs-2 col-xs-offset-2">
<div className="list-group">
{/* 编写路由链接 */}
<MyNavLink to="/about/a" children="about"/>
<MyNavLink to="/home" children="home"/>
</div>
</div>
<div className="col-xs-6">
<div className="panel">
<div className="panel-body">
{/* 注册路由 */}
<Switch>
<Route exact path="/about" component={About}/>
<Route exact={true} path="/home" component={Home}/>
</Switch>
</div>
</div>
</div>
给路由标签加上exact属性,它是一个布尔值,上述案例中,不添加exact依旧可以展示About组件,但添加后开启了严格匹配模式,/about/a和/about不一致,所以当点击about标签时就不会显示About组件。
11.7 redirect的使用
一般写在所有路由注册的最下方,当所有路由都无法匹配时,跳转到redirect指定的路由;
import {Switch,Route,Redirect} from 'react-router-dom'
<Switch>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Switch>
11.8 嵌套路由
1.注册子路由的时候要写上父路由的path值;
2.路由的匹配是按照注册顺序进行的(父级路由-->子路由);
<ul className="nav nav-tabs">
<li>
<MyNavLink to="/home/news" children="news"/>
</li>
<li>
<MyNavLink to="/home/messages" children="messages"/>
</li>
</ul>
3. 注册路由;
<Switch>
<Route path="/home/news" component={News} />
<Route path="/home/messages" component={Messages} />
<Redirect to="/home/news"/>
</Switch>
11.9 路由组件传参
11.9.1 向路由组件传递params参数
1. 携带参数:
<ul>
{
this.state.list.map(item => {
return (
<li key={item.id}>
{/* 1. 向路由组件传递参数 */}
<Link to={`/home/messages/message1/${item.id}/${item.content}`}>{item.content} </Link>
</li>
)
})
}
</ul>
2. 声明接收:
{/* 2. 声明接收params参数 */}
<Route path="/home/messages/message1/:id/:content" component={Details}></Route>
3. 接收参数
在Detail组件中:
// 3. 接收参数
const {id,content} = this.props.match.params;
11.9.2 向路由组件传递search参数
1. 携带参数
<Link to={`/home/messages/message1?id=${item.id}&content=${item.content}`}>{item.content}</Link>
2. 注册路由:无需声明,正常注册即可
<Route path="/home/messages/message1" component={Details}></Route>
3. 接收参数:获取的search是urlencoded编码字符,需要使用querystring解析
import qs from 'querystring'
const search = this.props.location.search.slice(1);
const {id,content} = qs.parse(search);
11.9.3 向路由组件传递state参数
它不会将传递的参数在地址栏中展示出来
1.携带参数:将to属性写成一个对象,pathname属性指定路由路径,state属性指定传递的参数
<Link to={{pathname:'/home/messages/message1',state:{id:item.id,content:item.content}}}>{item.content}</Link>
2.注册路由:
<Route path="/home/messages/message1" component={Details}/>
3.接收参数:
const {id,content} = this.props.location.state;
11.10 编程式路由导航
切换路由组件有时不一定依靠点击Link实现跳转,我们可以在其它的地方实现路由跳转
实现如下效果点击replace,以replace的方式跳转路由;点击push以push的方式跳转路由(replace跳转实际上是替换栈顶的路径,push跳转是将路径入栈);点击前进按钮前进到下一个页面路径,点击浏览器后退回上一个页面路径。
changeMode = (mode,id,content) => {
if(mode === 'replace') {
// params
// this.props.history.replace(`/home/messages/message1/${id}/${content}`);
// search
// this.props.history.replace(`/home/messages/message1?id=${id}&content=${content}`);
// state
this.props.history.replace('/home/messages/message1',{id,content});
} else if(mode === 'push') {
// this.props.history.push(`/home/messages/message1/${id}/${content}`);
// search
// this.props.history.push(`/home/messages/message1?id=${id}&content=${content}`);
// state
this.props.history.push('/home/messages/message1',{id,content});
}
}
forward = () => {
this.props.history.goForward();
}
back = () => {
this.props.history.goBack();
}
go = () => {
this.props.history.go(-2);
}
render() {
return (
<div>
<ul>
{
this.state.list.map(item => {
return (
<li key={item.id}>
<button onClick={() => this.changeMode('replace',item.id,item.content)}>replace</button>
<button onClick={() => this.changeMode('push',item.id,item.content)}>push</button>
{/* 1. 向路由组件传递参数 */}
{/* <Link to={`/home/messages/message1/${item.id}/${item.content}`}>{item.content}</Link> */}
{/* 1. 传递search参数 */}
{/* <Link to={`/home/messages/message1?id=${item.id}&content=${item.content}`}>{item.content}</Link> */}
{/* 1. 传递state参数 */}
<Link to={{pathname:'/home/messages/message1',state:{id:item.id,content:item.content}}}>{item.content}</Link>
</li>
)
})
}
</ul>
<hr />
{/* 2. 声明接收params参数 */}
{/* <Route path="/home/messages/message1/:id/:content" component={Details}></Route> */}
{/* 2. 接收search参数 */}
<Route path="/home/messages/message1" component={Details}></Route>
<button onClick={this.forward}>前进</button>
<button onClick={this.back}>后退</button>
<button onClick={this.go}>go</button>
</div>
)
}
总结:编程式路由导航实际上时使用了
11.11 withRouter
在react中存在两种组件,一般组件和路由组件。在一般组件中如果需要执行一些路由组件的API时,由于一般组件不存在props.history这个属性,操作无法进行。故需要withRouter的包装。
将上述案例中的前进后退按钮放在标题下方
在一般组件Topic中
import React, { Component } from 'react'
import { withRouter } from 'react-router';
class Header extends Component {
forward = () => {
this.props.history.goForward();
}
back = () => {
this.props.history.goBack();
}
go = () => {
this.props.history.go(-2);
}
render() {
return (
<div>
<div className="col-xs-offset-2 col-xs-8">
<div className="page-header">
<h2>React Router Demo</h2>
<button onClick={this.forward}>前进</button>
<button onClick={this.back}>后退</button>
<button onClick={this.go}>go</button>
</div>
</div>
</div>
)
}
}
export default withRouter(Header)
withRouter可以加工一般组件,让一般组件具备路由所有的API;
withRouter返回值时一个新组件;
11.12 BrowserRouter和HashRouter的对比
12. redux
12.1 redux的基本原理
12.2 求和案例
1. 首先取出掉count组件的自身状态
2. 在src下创建redux文件
count_action:用于创建action对象;
// 专门为count组件生成action对象
import {INCREMENT,DECREMENT} from './constant'
export const createIncrement = (data) => ({type:INCREMENT,data});
export const createDecrement = (data) => ({type:DECREMENT,data});
constant:存放type值,方便管理,减少错误;
// 用来定义action对象中中type类型的常量值,方便常量管理
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
3. 在store.js中
-引入createStore函数,创建一个store
-createStore函数调用时传入一个为其服务的reducer
-暴露store供外部使用
// 改文件用来暴露一个store对象
// 引入createStore,用于创建redux中最为核心的store对象
import {createStore} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer'
// 暴露store
export default createStore(countReducer);
4. count_reducer.js
-reducer本质是一个函数,两个参数:preState,action,返回值是加工后的状态;
-reducer两个作用:初始化状态,加工状态;
-reducer被第一次调用时,是store自动触发的,传递的preState是undefined;
// 该文件是用来为count组件服务的reducer,reducer的本质就是一个函数
// 该函数会接收到两个参数:之前的状态(preState),动作对象(action)
const initState = 0
export default function countReducer(preState=initState,action) {
// 从action中获取对象
const {type,data} = action;
// 根据type的类型决定如何加工数据
switch(type) {
case 'increment' :
return preState + data;
case 'decrement' :
return preState - data;
default :
return preState;
}
}
5. count.jsx中,需要导入store,在需要改变状态的回调中,这里以handleOddPlus为例,使用store.dispatch(action)修改状态,使用store.getState()获取当前的状态(即reducer的返回值);
import React, { Component } from 'react'
import store from '../../redux/store'
import './index.css'
export default class Add extends Component {
handlePlus = () => {
const {value:input} = this.selector;
// dispatch参数传递一个action对象
// select表单拿到的value值是字符串
store.dispatch({type:'increment',data:input * 1});
}
handleMinus = () => {
const {value:input} = this.selector;
store.dispatch({type:'decrement',data:input * 1});
}
handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = store.getState();
if(count % 2 !== 0){
store.dispatch({type:'increment',data:input * 1});
}
}
handlePlusAsync = () => {
const {value:input} = this.selector;
setTimeout(() => {
store.dispatch({type:'increment',data:input * 1});
},2000)
}
render() {
return (
<div className="wrapper">
<div className="wrapper-title">
<h2>当前求和为:{store.getState()}</h2>
</div>
<select ref={c => this.selector = c} name="select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.handlePlus}>加</button>
<button onClick={this.handleMinus}>减</button>
<button onClick={this.handleOddPlus}>当前时奇书加</button>
<button onClick={this.handlePlusAsync}>异步加</button>
</div>
)
}
}
6. 在index.js中检测store的状态改变,一旦发生改变重新渲染<App/>;
redux值负责状态管理,至于状态的改变驱动页面的展示需要自己实现。
// 引入react核心库
import React from 'react'
// 引入reactDOM
import ReactDOM from 'react-dom'
// import {BrowserRouter} from 'react-router-dom'
import store from './redux/store'
// 引入App组件
import App from './App'
// 渲染App组件到页面
ReactDOM.render(<App/>,document.getElementById('root'));
// 检测redux中的状态变化,变化就调用render
store.subscribe(() => {
ReactDOM.render(<App/>,document.getElementById('root'));
});
12.3 异步action
当延迟的动作不想交给组件自身,而是交给action;
何时需要异步action:想要对状态进行操作,但是具体的数据靠异步任务返回;
实现过程:
1. 安装:npm i redux-thunk
2. 在store中做相关配置:需要额外引入applyMiddleware和安装的redux-thunk,将applyMiddleware作为createStore的第二个参数;
// 引入createStore,用于创建redux中最为核心的store对象
import {createStore,applyMiddleware} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './count_reducer'
// 引入redux-thunk用于支持异步actions
import thunk from 'redux-thunk'
// 暴露store
export default createStore(countReducer,applyMiddleware(thunk));
3. 异步任务有结果后,分发一个同步的action去真正操作数据;
conut.jsx
handlePlusAsync = () => {
const {value:input} = this.selector;
store.dispatch(createIncrementAsync(input * 1,2000));
// setTimeout(() => {
// store.dispatch(createIncrement(input * 1));
// },2000)
}
count_action.js
export const createIncrement = (data) => ({type:INCREMENT,data});
export const createIncrementAsync = (data,time) => {
return (dispatch) => {
setTimeout(() => {
dispatch(createIncrement(data));
},2000);
}
}
在conut组件中调用dispatch后,store会**获得一个函数作为执行结果**,通过安装中间件thunk,使得store去执行这个函数(即createIncrementAsync返回的箭头函数),该函数的函数体是一个异步任务,该异步任务执行完毕,再次调用dispatch,将真正的action对象传递给store。
12.4 react-redux
UI组件:不适用任何redux的api,只负责页面的呈现和交互;
容器组件:负责和redux通信,将结果递交给UI组件;
修改求和案例:
1.创建容器组件:
在src布鲁下新建container文件夹,用来存放容器组件;
创建一个容器组件,依靠react-redux的connect函数
connect(mapStateToProps,mapDispatchToProps)(UI组件),需要传递两个参数,类型是函数;
-mapStateToProps:**映射状态**,返回一个对象;
-mapDispatchToProps:**映射操作状态的方法**,返回值是一个对象;
定义这两个函数,return的对象的属性**会以props的形式传递给UI组件**
container/Count/index.jsx:
// 引入count的UI组件
import CountUI from '../../Components/Add'
import {createIncrement,createDecrement,createIncrementAsync} from '../../redux/count_action'
// 引入connect用于链接UI组件和redux
import {connect} from 'react-redux'
// 该函数返回的是一个对象
// 返回对象的key就作为传递给UI组件props的keys,返回的value就作为传递给UI组件props的value
// 该函数用于传递状态
function mapStateToProps(state) {
return {count:state}
}
// 该函数返回的是一个对象,用于传递状态的方法
function mapDispatchToProps(dispatch) {
return {
plus: (number => dispatch(createIncrement(number))),
minus: (number => dispatch(createDecrement(number))),
plusAsync: ((number,time) => dispatch(createIncrementAsync(number,time)))
}
}
// 使用connect创建暴露一个Count的容器组件
export default connect(mapStateToProps,mapDispatchToProps)(CountUI)
2.在容器组件上级组件引入store
容器组建的store是靠props传递进去的,而不是直接在容器组件中import引入,需要在容器组件父组件中书写如下代码;
app.js:
import React,{Component} from "react";
import Add from './container/Conut'
import store from './redux/store'
export default class App extends Component {
render() {
return (
<Add store={store}/>
)
}
}
3.修改component/Count/index.jsx:
在UI组件中用props回去容器组件传来的状态和操作状态的方法
handlePlus = () => {
const {value:input} = this.selector;
this.props.plus(input * 1);
}
handleMinus = () => {
const {value:input} = this.selector;
this.props.minus(input * 1);
}
handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = this.props.count;
if(count % 2 !== 0){
this.props.plus(input * 1);
}
}
handlePlusAsync = () => {
const {value:input} = this.selector;
console.log(this.props);
this.props.plusAsync(input * 1,2000);
}
在控制台上输出this.props可见:
12.4.1 connect 参数优化
container精简版:
将函数直接写在connect的形参位置,mapDispatchToProps可以写成一个对象;
export default connect(
state => ({count:state}),
{
plus:createIncrement,
minus:createDecrement,
plusAsync:createIncrementAsync
})(CountUI)
12.4.2 Provider组件
上述案例中容器组件的store是通过该容器组件的标签属性传递<Add store={store}/>,但如果容器组件的数量一多,需要挨个进行传递,而在react-redux中提供了Provider组件给需要store的容器组件传值。
app.js
import { Provider } from "react-redux";
export default class App extends Component {
render() {
return (
<Provider store={store}>
<Add />
</Provider>
)
}
}
另外,由于使用了react-redux的缘故,在index.js中无需在根组件外侧使用store.subscribe()进行监测页面的改变,即可以删你出上述案例的如下代码:
store.subscribe(() => {
ReactDOM.render(<App/>,document.getElementById('root'));
});
优化:
1. 容器组件个UI组件整合成一个组件;
// 引入count的UI组件
import {createIncrement,createDecrement,createIncrementAsync} from '../../redux/count_action'
// 引入connect用于链接UI组件和redux
import {connect} from 'react-redux'
import React, { Component } from 'react'
import './index.css'
class Add extends Component {
handlePlus = () => {
const {value:input} = this.selector;
this.props.plus(input * 1);
}
handleMinus = () => {
const {value:input} = this.selector;
this.props.minus(input * 1);
}
handleOddPlus = () => {
const {value:input} = this.selector;
// 获取当前状态
const count = this.props.count;
if(count % 2 !== 0){
this.props.plus(input * 1);
}
}
handlePlusAsync = () => {
const {value:input} = this.selector;
console.log(this.props);
this.props.plusAsync(input * 1,2000);
}
render() {
return (
<div className="wrapper">
<div className="wrapper-title">
<h2>总价格为:{this.props.count}</h2>
</div>
<select ref={c => this.selector = c} name="select">
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<button onClick={this.handlePlus}>加</button>
<button onClick={this.handleMinus}>减</button>
<button onClick={this.handleOddPlus}>当前时奇书加</button>
<button onClick={this.handlePlusAsync}>异步加</button>
</div>
)
}
}
export default connect(
state => ({count:state}), // 映射状态,参数state就是redux传给组件的状态,相当于调用了store.getState
{ // 映射操作状态的方法
plus:createIncrement,
minus:createDecrement,
plusAsync:createIncrementAsync
})(Add)
12.4 .3 使用redux实现多组件通信
1.修改store:
若需要store负责多个组件之间的通信,其createStore第一个参数应该是多个reducer的总状态,总状态需要combineReducer进行合并,合并后的总状态是一个对象。
// 引入createStore,用于创建redux中最为核心的store对象
import {createStore,applyMiddleware,combineReducers} from 'redux'
// 引入为count组件服务的reducer
import countReducer from './reducer/count_reducer'
import personReducer from './reducer/person_reducer'
// 引入redux-thunk用于支持异步actions
import thunk from 'redux-thunk'
const allReducer = combineReducers({
count:countReducer,
person:personReducer
})
// 暴露store
export default createStore(allReducer,applyMiddleware(thunk));
2.在容器组件中获取状态需要写明是从哪个reducer中获取的状态,即combineReducers中的属性名;
export default connect(
state => ({personList:state.person,total:state.count}),
{
addPerson:createPerosn,
}
)(person)
12.5 纯函数
只要是同样的输入,必定会得到同样的输出返回;
必须遵守以下一些约束:
不得改写参数数据
不会产生任何副作用,例如网络请求,输入和输出设备;
不能调用Date.now()或者Math.random()等不纯的方法;
redux的reducer函数必须是一个纯函数。
12.6 redux开发者工具
1. 安装:npm i redux-devtools-extension
2. store.js进行配置:
import {composeWithDevTools} from 'redux-dextools-extension'
const store = createStore(allReducer,composeWithDevTools(applyMiddleware(thunk)));
3.chrome安装扩展程序
这边选择从本地加载:
添加到任务栏:
13 lazyLoad 懒加载
react的懒加载一般使用在路由组件加载中,为了让路由组件点击时再请求组件资源。
import React, { Component,lazy,Suspense } from 'react'
import Loading from './pages/Loading'
const About = lazy(() => import('./pages/About'));
const Home = lazy(() => import('./pages/Home'));
```
```react
{/* 注册路由 */}
<Suspense fallback={<Loading />}>
<Route path="/about" component={About}/>
<Route path="/home" component={Home}/>
<Redirect to="/about"/>
</Suspense>
Suspense标签需要添加fallback属性,用来展示需要加载的组件处于pending状态时展示的内容,其导入方式不能写成懒加载。
14 hooks
hooks是16.8版本的新特性,可以在函数式组件中使用state及其它的react特性;
三个常用的hook:
15.1.state hook
React.useState(init);
该方法的调用能返回一个数组,数组中只有两个元素 ,
第一个是状态,第二个是改变状态的方法; 该方法会调用多次,第一次调用会将第一个值缓存下来,所以后续调用状态不会被init覆盖;
<script type="text/babel">
function Demo() {
const [count,setCount] = React.useState(0);
const [name,setName] = React.useState('Jack');
function add() {
// 写法一:参数直接指定新的状态
setCount(count + 1);
// 写法二:传递一个函数,参数是之前的状态值,返回修改后的状态
setCount(count => count + 1);
}
function changeName() {
setName(name => 'Tom');
}
return (
<div>
<h2>当前求和为:{count}</h2>
<h2>我的名字是:{name}</h2>
<button onClick={add}>+ 1</button>
<button onClick={changeName}>改名</button>
</div>
)
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
</script>
15.2 Effect hook
可以在函数组件中模拟生命周期钩子。
语法说明:
React.useEffect(() => {
// 在再次可以执行任何副作用操作
return () => {} // 组件卸载时执行
},[stateValue]);
第一个参数是一个函数,该函数相当于:componentDidMount(),componentDidUpdate(),componentwillUnmount();该函数返回一个匿名函数,该函数会在组件卸载时执行相当于componentwillUnmount();函数存在第二个参数,该参数是一个数组,数组元素为监视的状态。
1.如果数组中有元素,则该函数第一个参数同时充当componentDidUpdate(),即当数组状态元素发生改变时触发;
2,如果为空数组则第一个参数不会监视任何状态的改变,不具有componentDidUpdate()功能;
3.如果无第二个参数,则参数一会监视函数组件所有状态的变化。```react
function Demo() {
const [count,setCount] = React.useState(0);
const [name,setName] = React.useState('Jack');
function add() {
// 写法一:参数直接指定新的状态
setCount(count + 1);
// 写法二:传递一个函数,参数是之前的状态值,返回修改后的状态
setCount(count => count + 1);
}
React.useEffect(() => {
let timer = setInterval(() => {
setCount(count => count + 1);
},1000);
return () => {
clearInterval(timer);
}
},[]);
function changeName() {
setName(name => 'Tom');
}
function unmount() {
ReactDOM.unmountComponentAtNode(document.getElementById('test'));
}
return (
<div>
<h2>当前求和为:{count}</h2>
<h2>我的名字是:{name}</h2>
{/*<button onClick={add}>+ 1</button>*/}
<button onClick={changeName}>改名</button>
<button onClick={unmount}>卸载组件</button>
</div>
)
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
15.3 Ref hook
function Demo() {
const myRef = React.useRef();
function showData() {
alert(myRef.current.value)
return (
<div>
<input type="text" ref={myRef}/>
<button onClick={showData}>提示输入数据</button>
</div>
)
}
ReactDOM.render(<Demo/>,document.getElementById('test'));
16 Context
能实现组件之间的通信,**祖组件和后代组件之间**的通信。
const MyContext = React.createContext();
class A extends React.Component {
state = {
username:'zs',
age:19
}
render(){
const {username,age} = this.state;
return (
<div className="a">
<h1>这是A组件</h1>
<h1>我的用户名为:{username}</h1>
<MyContext.Provider value={{username,age}}>
<B/>
</MyContext.Provider>
</div>
)
}
}
class B extends React.Component{
render(){
return (
<div className="b">
<h1>这是B组件</h1>
<C/>
</div>
)
}
}
class C extends React.Component{
static contextType = MyContext;
render(){
console.log(this.context);
return (
<div className="c">
<h1>这是C组件</h1>
<h1>接收到A组件传来的username为:{this.context.username}</h1>
</div>
)
}
}
ReactDOM.render(<A/>,document.getElementById('root'));
若组件C为函数式组件:
function C() {
return (
<div className="c">
<h1>这是C组件</h1>
<h1>接收到A组件传来的username为:
<MyContext.Consumer>
{value => value.username}
</MyContext.Consumer>
</h1>
</div>
)
}
17. 组件优化
一般情况下,只要执行了setState(),即使不改变状态数据,组件也会重新render(),这样效率比较低;只要当前组件重新render了就会自动冲新render子组件,纵使子组件没有用到父组件的任何数据。
优化方案:只有当组件的state和props数据发生改变的时候才会重新render(),那是由于shouldComponentUpdate()总是返回true。
解决:
这只是进行了数据的浅比较,如果只是数据对象内部发生了改变,返回false
代码:
18. renderProps
作用:向组件内部传入带有动态内容的结构;<=> vue中插槽技术;
1. 使用children props通过组件标签体传入结构;
如果B组件需要A组件中的数据无法做到。
2. 使用render props 通过组件标签属性传入结构,一般用render函数属性;
class Demo extends React.Component {
render(){
return (
<A render={(data)=> <B data={data}></B> }></A>
)
}
}
class A extends React.Component {
state = {name:'zs'}
render(){
const {name} = this.state;
console.log(name);
return(
<div>
<p>这是A组件</p>
{this.props.render(name)}
</div>
)
}
}
class B extends React.Component {
render(){
console.log(this.props.data);
return(
<div>
<p>这是B组件</p>
<p>{this.props.data}</p>
</div>
)
}
}
ReactDOM.render(<Demo/>,document.getElementById('root'));
19.错误边界
子组件中出错,会影响到父组件,为了控制错误不影响到父组件,引入错误边界
错误边界可以捕获后代组件生命周期返回的错误;
错误边界始终找出错组件的父组件去处理;