react入门
组件的两种实现模式:
1.函数式组件(相比较函数式性能会更好)
import React from 'react'
export default ()=>{
return (
<div id="app">
<h1>hello world</h1>
</div>
)
}
2.class组件
// react组件(class组件)
import React, {Component} from 'react'
export default class App extends Component{
// 如果继承了Component
// 就必须实现render函数,这个render函数,提供了组件的dom结构
render(){
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
</div>
)
}
}
react的指令实现(jsx语法 比较与vue指令实现方式)
v-text
只要在jsx的dom中,作用域可以访问到的变量,可以解析出来的表达式,就可以使用’{表达式}'表示
import React, {Component} from 'react'
let value1 = 'value1......';
export default class App extends Component{
constructor(){
super();
this.value3 = 'value3.......';
}
render(){
let value2 = 'value2........';
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
<p>{value1}</p>
<p>{value2}</p>
<p>{this.value3}</p>
</div>
)
}
}
v-html
在react中没有innerHTML的概念
如果有一段html字符串,需要自己去解析成react dom对象
需要使用 dangerouslySetInnerHTML
export default class App extends Component{
render(){
const dom = '<span>富文本 <i>斜体</i><b>粗体</b></span>';
const renderDom={
__html:dom //必须这样的格式写
};
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
{dom} //这样写不会渲染成html标签,会自动转义展示 直接展示:<div className="box">....文本
<div dangerouslySetInnerHTML={renderDom}></div> //这样可以显示成功
</div>
)
}
}
v-if
const isExist = true;
export default class App extends Component{
render(){
let box = <div style={{width: '100px', height: '100px', background: 'red'}}></div>;
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
{ isExist ? box : '' }
{ isExist && box }
</div>
)
}
}
v-else-if
const flag = 'C';
export default class App extends Component{
render(){
let box1 = <div style={{width: '100px', height: '100px', background: 'red'}}>A</div>;
let box2 = <div style={{width: '100px', height: '100px', background: 'yellow'}}>B</div>;
let box3 = <div style={{width: '100px', height: '100px', background: 'green'}}>C</div>;
let box4 = <div style={{width: '100px', height: '100px', background: 'blue'}}>D</div>;
let result = null;
// switch (flag) {
// case 'A':
// result = box1;
// break;
// case 'B':
// result = box2;
// break;
// case 'C':
// result = box3;
// break;
// case 'D':
// result = box4;
// break;
// }
if(flag === 'A'){
result = box1;
}else if(flag == 'B'){
result = box2;
}else if(flag == 'C'){
result = box3;
}else if(flag == 'D'){
result = box4;
}
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
{result}
</div>
)
}
}
v-bind
v-bind属性绑定
直接设置标签的属性为一个{表达式}
绑定class,正常写class的表示方式:多个class属性值之间使用空格隔开
绑定style 绑定一个对象
export default class App extends Component{
render(){
let className = 'hello';
let title = 'hello react';
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
<div className={className} title={title}> </div>
<div style={{width: '100px', height: '100px', background: 'red'}}></div>
</div>
)
}
}
v-show
操作标签的display属性为none
<div style={{width: '100px', height: '100px', background: 'red', display: isShow ? '': 'none'}}></div>
v-for
数组我们通常用map
其他操作使用自运行函数
删除数组使用filter
添加使用扩展运算符(…)
let list = ['A', 'B', 'C', 'D', 'E'];
export default class App extends Component{
render(){
let listDOM = list.map((item)=>{
return <p key={item}>{item}</p>
});
return (
<div id="app">
<h1>hello world~~~~~~~~~~</h1>
{/* {
list.map((item)=>{
return <p>{item}</p>
})
} */}
{listDOM}
</div>
)
}
}
v-on
//需要指正this指向:
this.handleClick = this.handleClick.bind(this);
<button onClick={this.handleClick}>
Click me
</button>
———————————————————————————————————————————————————————————
//可以使用class fields语法
handleClick = () => {
console.log('this is:', this);
}
...
<button onClick={this.handleClick}>
Click me
</button>
———————————————————————————————————————————————————————————
箭头函数语法不推荐:(每次渲染组件时都会创建不同的回调函数,对性能不友好)
<button onClick={(e) => this.handleClick(e)}>
Click me
</button>
———————————————————————————————————————————————————————————
传递参数:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
v-model
在react中,没有数据双向绑定
只有单向绑定,js的数据绑定给dom,dom的变化不接受
state的基本操作
在vue中:
内部属性 data
外部属性 props
计算属性 computed
全局属性vuex state
在react中
内部属性state:只能在constructor方法中定义
只有在constructor可以设置this.state = {…}
其他函数,都不能直接操作this.state = {…}
如果需要修改state,那么请使用this.setState({…})方法,并且在componentDidMount之后操作
在update相关的所有更新的生命周期方法中都不能操作this.setState({…})
export default class App extends Component{
constructor(){
super();
// 定义组件内部属性,state
this.state = {
value: 'hello world',
a: 1,
b: 2,
c: 3
}
}
render(){
console.log('render.....');
return (
<div id="app">
<h1>{this.state.value}</h1>
<h1>{this.state.a}</h1>
<h1>{this.state.b}</h1>
<h1>{this.state.c}</h1>
<button onClick={this.btnAction.bind(this)}>修改</button>
</div>
)
}
btnAction(){
console.log('点击了修改');
// 修改state
this.setState({
value: 'hi world'
});
}
// mounted 挂载后
componentDidMount(){
// this.state = {
// value: 'hi world'
// }
}
}
react中的受控组件和非受控组件
表单中有一个input标签,input的value值必须是我们设置在constructor构造函数的state中的值,然后,通过onChange触发事件来改变state中保存的value值,这样形成一个循环的回路影响
就是v-model的功能,在react中需要自己去实现
需要自己实现数据双向绑定
数据绑定在表单标签上,必须绑定state的值,不能是普通的值
然后需要实现表单的change事件,在事件中接收ev,获得表单的value值(ev.target.value
将接收到的value值设置给state上,state变了,界面就会变
非受控也就意味着我可以不需要设置它的state属性,而通过ref来操作真实的DOM。
在react中,只有input=(text number email。。。。) textarea select(单选) 标签可以做成受控组件
input=(radio checkbox…)不能做成受控组件
受控组件都是一个特点
1.绑定value值
2.实现change事件,事件内,接收ev.target.value,重新设置给state中绑定的value值
export default class App extends Component{
constructor(){
super();
this.state = {
value: '123'
}
}
render(){
return (
<div id="app">
{/* 受控组件 */}
<input type="text" value={this.state.value} onChange={this.changeAction.bind(this)}/>
{/* 非受控组件 */}
<input type="text" ref="input"/>
<button onClick={this.btnAction.bind(this)}>获取</button>
</div>
)
}
changeAction(ev){
console.log('changeAction run......');
this.setState({
value: ev.target.value
});
}
btnAction(){
console.log('btnAction run ........');
console.log(this.refs.input.value);
}
}
可实现的受控组件
export default class App extends Component{
constructor(){
super();
this.state = {
value: '123',
selected: 'B'
}
};
render(){
let {value, selected} = this.state;
let changeAction = this.changeAction.bind(this);
return (
<div id="app">
<input type="text" value={value} onChange={changeAction}/>
<textarea value={value} onChange={changeAction}></textarea>
<select value={selected} onChange={this.selectChangeAction.bind(this)}>
<option value="A">A</option>
<option value="B">B</option>
<option value="C">C</option>
<option value="D">D</option>
</select>
</div>
)
};
changeAction(ev){
console.log('changeAction run......');
this.setState({
value: ev.target.value
});
};
selectChangeAction(ev){
this.setState({
selected: ev.target.value
})
}
}
实现radio标签的数据绑定
export default class App extends Component{
constructor(){
super();
this.state = {
gender: '男'
}
};
render(){
let {gender} = this.state;
return (
<div id="app">
<label>
<input type="radio" value="男" onChange={this.radioAction.bind(this)}
checked={gender==='男'}/>
男
</label>
<label>
<input type="radio" value="女" onChange={this.radioAction.bind(this)}
checked={gender==='女'}/>
女
</label>
</div>
)
};
radioAction(ev){
console.log(ev.target.value, ':');
console.log(ev.target.checked);
if(ev.target.checked){
this.setState({
gender: ev.target.value
})
}
}
}
实现checkbox标签的数据绑定
export default class App extends Component{
constructor(){
super();
this.state = {
likes: ['吃']
}
};
render(){
let {likes} = this.state;
console.log(likes);
console.log('render......');
return (
<div id="app">
<p>数据:{likes}</p>
<label>
<input type="checkbox" value="吃" onChange={this.checkboxAction.bind(this)}
checked={ (()=>{
return this.state.likes.findIndex(item=>item==='吃') >= 0
})() }/>
吃
</label>
<label>
<input type="checkbox" value="喝" onChange={this.checkboxAction.bind(this)}/>
喝
</label>
<label>
<input type="checkbox" value="玩" onChange={this.checkboxAction.bind(this)}/>
玩
</label>
<label>
<input type="checkbox" value="乐" onChange={this.checkboxAction.bind(this)}/>
乐
</label>
</div>
)
};
checkboxAction(ev){
if(ev.target.checked){
//选中:将value放在数组中
// 直接操作state,是错误的
// this.state.likes.push('value');
// 通过setState修改state中的数据,是正确的
this.setState({
likes: [...this.state.likes, ev.target.value]
});
}else{
//取消选中:将value从数组中拿出来
// this.state.likes 删除掉其中一个,ev.target.value
this.setState({
likes: this.state.likes.filter(item=>(item !==ev.target.value))
})
}
}
}
组件的传值
1.父子组件:
传值:
1.父传子:在父组件中,设置自定义属性,子组件使用porps进行接收
props外部属性,需要采用单向数据流。props只可以访问,不能修改
2.子传父:绑定自定义属性,但是属性值为函数。在子组件中,通过props调用该函数,
通过函数传参的形式将值传给父组件
2.非父子组件
同vue,使用发布订阅模式
1.new Vue
$on
$emit
2.自己实现发布订阅模式
3.pubsub.js
父传子
Father.js
export default class Father extends Component {
constructor(){
super();
this.state = {
list: ['A', 'B', 'C', 'D'],
selected: 'A',
selectCity: null
}
}
render() {
return (
<div className="father">
<h1>Father组件</h1>
<nav>
{
this.state.list.map(item=>(
<button key={item} onClick={()=>{
this.setState({selected: item});
}}>{item}</button>
))
}
</nav>
<p>选择的城市为:{this.state.selectCity}</p>
<hr/>
<Son title="hello wrold" data={this.state.selected} onSelectCity={this.handleSelectCity.bind(this)}/>
</div>
)
}
handleSelectCity(item){
console.log('handleSelectCity执行了........', item);
console.log(this);
this.setState({
selectCity: item
});
}
}
Son.js
export default class Son extends Component {
//props
constructor(props) {
super(props);
this.state = {
cities: ['深圳', '北京', '广州']
}
}
render() {
return (
<div className="son">
<h1>Son组件</h1>
<p>{this.props.title}</p>
<p>{this.props.data}</p>
<nav>
{
this.state.cities.map(item=>(
<button key={item} onClick={()=>{
this.props.onSelectCity(item);
}}>{item}</button>
))
}
</nav>
</div>
)
}
}
React.Components生命周期函数
挂载:
constructor()
构造函数
构造函数必须调用super()
在构造函数中,如果需要访问props,直接调用参数中的props 在构造函数中,设置state的初始值
在构造函数中,给多次使用的函数bind(this)
以及其他的一些初始化操作
static getDerivedStateFromProps()
//相当于计算属性
render()
//在这个方法中,不能使用setState 需要返回组件的dom
componentDidMount()
//挂载后 mounted 通常在这个方法中,请求数据,操作dom
更新:
只有调用了setState组件才会执行更新!!!!!!!!
1.props发生变化,执行更新:
UNSAFE_componentWillReceiveProps(newProps) 废弃了
static getDerivedStateFromProps()
16.0+
shouldComponentUpdate()
\
shouldComponentUpdate(newProps, newState, newContext){
console.log(this.props, this.state, this.context);//旧值
在这个方法中,对比数据的新旧值,对render方法进行拦截,减少render的操作,可以优化react组件的性能
}
render()
得到了新的dom
UNSAFE_componentWillUpdate() 废弃了
getSnapshotBeforeUpdate()
16.0+\
如果你在componentDidUpdate方法中有复杂的逻辑操作,数据处理可以放在getSnapshotBeforeUpdate中处理之后传给componentDidUpdate,而componentDidUpdate就只需要处理dom了
参数列表是旧值 实现了这个函数,就必须实现componentDidUpdate
componentDidUpdate()
//updated
componentDidUpdate(oldProps, oldState, result) result是getSnapshotBeforeUpdate中return的值
2.state发生变化,执行更新:
static getDerivedStateFromProps() 16.0+
shouldComponentUpdate()
render() 得到了新的dom
UNSAFE_componentWillUpdate() 废弃了
getSnapshotBeforeUpdate() 16.0+
componentDidUpdate()
如果执行了 强制更新 forceUpdate: 不询问,直接更新
static getDerivedStateFromProps() 16.0+
render() 得到了新的dom
UNSAFE_componentWillUpdate() 废弃了
getSnapshotBeforeUpdate() 16.0+
componentDidUpdate()
卸载
componentWillUnmount
react数据不可变性(时间旅行)
react中的数据(state)需要拥有不可变性
不可变性:一旦声明,设置了值,之后不能修改,这个值相当于一个快照,相当于一帧表示当前的的一个状态
之后如果需要修改,先将数据拷贝出来成为一个新的数据或引用类型,再修改。
虚拟dom
虚拟dom全部需要重新构建,然后再比较不同,发现不同的地方,再根据不同的虚拟dom构建真实dom,用真实的dom替换页面不同的地方
react数据发生变化的时候所有组件的rander函数都会被触发,构建虚拟dom进行比较
事实上明知道是没有修改的,++连虚拟dom的创建和比较也可以避免掉++\
shouldComponentUpdate(newProps, newState){
// console.log('旧值:', this.props, this.state);
// console.log('新值:', newProps, newState);
// 判断this.props和newProps是否不同
// 判断this.state和newState是否不同
// 判断引用地址一样不一样,如不一样就是改了,如一样就是没改
浅比较,只比较props和state的第一层数据的值
const result1 = this.compareData(newProps, this.props);
const result2 = this.compareData(newState, this.state);
return !(result1 && result2);
}
// 比较两个对象,如果都相同,那么返回值为true
// 比较两个对象,如果不相同,那么返回值为false
compareData(newVal, oldVal){
let newKeys = Object.keys(newVal);
let oldKeys = Object.keys(oldVal);
let result = true;
if(newKeys.length === oldKeys.length){
//修改某个值
// 删除一个,新增一个
newKeys.forEach(key=>{
if(newVal[key] !== oldVal[key]){
result = false;
}
})
}else{
result = false;//新增了,删除了
}
return result;
}
不可变对象的修改方式
this.setState({
obj:{
...this.state.obj,
b:3
}
})
1.简化复杂的功能
2.跟踪数据的变化
3.确定React何时修改
PureComponen(它不止阻挡自己的更新,子组件的也会阻挡掉)
纯组件相对于Component组件,已经实现了上面的shouldComponentUpdata,不需要开发者实现
怎么实现:
就是比较state和props的新旧值,实现的是浅比较只比较第一层,只比较引用是否相等,不比较内容
react其他属性
setState
react是异步更新dom的
vue是同步更新dom的
异步更新数据和dom,如果setState之后,立即操作state,得不到新的值
需要在setState第二个参数的回调函数中操作,才是更新后的结果
this.setState({
val:this.state.val+1 // 接下来会执行componentDidUpdate
val2:this.state.val2 / this.state.val //由于setState是异步更行,this.state.val并不能确定是初始值还是计算后的值,所以不建议这样使用(百分之99不会有问题会是初始值)
},()=>{
...
//componentDidUpdate 执行完之后执行
});
异步计算的不确定性,提议这样写:
this.setState((state, props)=>{
return {
val: state.val+1,
val2: this.state.val2 / state.val
}
});
简化写法:
this.setState((state, props)=>({
val: state.val+1,
val2: this.state.val2 / state.val
}));
class属性
defaultProps
配置外部属性的props的默认值
App.defaultProps = {
title: '我的react项目'
}
写在class里面:
static defaultProps = {
title: '我的react项目'
}
displayName
配置组件的名字
App.displayName = 'Root';
写在class里面:
static displayName = 'Root';
react的高级指引
代码分割(模块/组件按需引入)
普通模块可以按需引入
只要支持es6模块化的环境都可以使用import按需引入
import('./utils/tools').then(({mapArray})=>{
let newArr = mapArray(['a', 'b', 'c'], (item, index)=>(item+index));
console.log(newArr);
})
组件按需引入
这里有一个Box组件,我需要在App组件中懒加载这个Box组件
之前在vue中:
const Box=()=>import("./Box");
即可使用,vue中会自动帮你解析promise,但是在React中不行
在react中:
import("./math").then(math => {
console.log(math.add(16, 26));
});
React.lazy 可以实现组件的懒加载,但是是react16.0之后新增的功能
const Box=React.lazy(()=>import("./Box"));
凡是懒加载的部分,都需要使用lazy
<React.Suspense fullback={<div>loading......</div>}>
凡是有懒加载组件的部分,都需要Suspense组件包裹,并且Suspense组件需要实现fallback属性
fallback的值,当Suspense组件识别到自己的子节点有懒加载的组件,
使用 fallback的结构 先替换组件所在位置,当组件加载出来了,再使用真正的组件。
</React.Suspense>
懒加载载入的组件不能是export const输出,必须使用export default
在react中使用scss直接安装node-scss,不需要配置,引用直接引用.scss
context
React.context
使用context的三个步骤
1.创建context
React.createContext({
title:'my react project' //设置默认值一般不需要设置
})
2.设置提供context
AppContext.Provider
<AppContext.Provider value={{title: 'hello world'}}>
<div className="foo">
...
</div>
</AppContext.Provider>
3.使用context
1.contextType
设置了context的类型,该组件,会在自己的组件树一层一层向上找该类型的Provider,找到了该类型的Provider使用Provider提供的value数据,找不到,就使用该类型的默认值
Item.contextType=AppContext;
2.AppContext.Consumer
<AppContext.Consumer>
{(context)=>{
return <mark>{context.title}</mark>
}}
</AppContext.Consumer>
错误边界
在vue中会使用errorCaptuer 捕获错误
在react中有两个 处理错误的方法,这两个方法只能在根文件中使用
getDeriveStateFromError
他的this指向组件class,不需要this就使用这个静态方法
static getDeriveStateFromError(error){
1.显示错误提示界面
2.收集错误,提交给后台
return {
isError:true
}
}
componentDidCatch
需要使用this就是用这个,指向实例
componentDidCatch(error){
// 1.显示错误提示界面
// 2.收集错误,提交给后台
this.setState({
isError: true
})
}
实际使用将error单独封装成一个组件,然后让error组件去包裹App
这个error的组件就是错误边界
Error.js
render(){
console.log(this.props.children);
if(!this.state.isError){
//没有错
return this.props.children; //组件中包含的内容
}else{
return(
<div>
出错了
<button onClick={this.goHomeAction.bind(this)}>回到首页</button>
</div>
)
}
}
goHomeAction(){
this.setState({isError: false}, ()=>{
//切换到首页
})
}
componentDidCatch(){
this.setState({isError: true});
}
index.js 中 使用
<Error>
<App/>
</Error>
refs和 DOM
ref的基本使用
ref中有设置多个同名,就会获取到最后的一个,不管是普通结构还是循环出来的结构(在vue中循环出来的结构会放在一个数组中)
如果想操作多个:
[1,2,3].map(item=>{
<Box key={item} ref={"box-"+item}>
})
给普通标签设置ref,得到的使用dom对象,可以操作dom
react中操作ref是会影响性能的,他的ref是通过闭包来实现的
闭包嵌套得到的refs的这个对象,会消耗性能
解决:(推荐使用这种函数的形式操作ref
,不要用上面的字符串的方式)
普通标签:
<h1 ref={
(el)=>{
console.log(el);
}
}>app组件</h1>
组件:
<Box ref={
(com)=>{
console.log(com)
this.box=com;
}
}></Box>
16.3+推出了另外一种形式
createRef
contructor(){
this.el=createRef();
}
<h1 ref={this.el}>
componentDidMount(){
this.el.current.style.color = 'red';
}
元素和组件使用方式是一样的
回调函数的方式和createRef这两个方法都可以,不要使用字符串的方式
ref的转发
在父组件上设置ref,要获取到上面的子元素/组件
注意:
*函数式组件不能设置ref,ref也不能在函数式组件中设置*
React.forwardRef
在父组件中设置了ref给这个wrap组件
ref的转发,不在于设置的ref值是什么,关键在于设置了ref的组件内部,需要使用React.forwardRef()包裹函数式组件,函数式组件的参数第一个还是props,还多了第二个参数,是外部接收到的ref,就可以设置转发给内部的结构
Wrap.js
export default React.forwardRef(
(props,ref)=>{ //props 会把ref给剔除掉
return(
<div>
<h3 ref={ref}></h3> //转发给了h3,外部获取的ref就不是wrap组件而是h3
</div>
)
}
)
Fragment
Fragment
相当于 Vue中的tempale
标签,不会在页面上渲染,是react的内置组件
<></>
这样写是Fragment的糖语法,不需要引入Fragment
它也是16.0+的新属性,之前的写法
render() {
return [
<h1 key="1">hello world</h1>,
<h2 key="2">hello world</h2>,
...
]
}
高阶组件
一般会用with开头 一个给普通组件增加功能的函数
本质上是一个函数
高阶组件内返回一个组件,接收到的参数需要有组件
返回的组件类型:函数式组件,React.forwardRef(()=>{});构建的组件,第二个参数是个对象
export default(Com)=>{
return class widthTest extends Component(
render(){
return{
<Com/>
}
}
)
}
组件使用:
Foo.js
import {withTest} from '../HOC/withTest'
...
export default withTest(Foo);
使用场景:
- 统一的功能可以在高阶组件上实现
- 统一样式
- 共享数据
高阶组件有两个弊端:
1.传值问题props
2.ref选择问题
Portals
类似于Vue中的$mount 动态挂载
constructor(props) {
super(props);
this.el = document.querySelector('#modal-root');
}
render() {
let dom = (
<div id="modal" style={{width: '100px', height: '100px', background: 'red'}} onClick={this.tapAction.bind(this)}></div>
)
return ReactDOM.createPortal(
dom,
this.el
);
}
Profiler
测量树中这部分渲染所带来的开销
<Profiler id="app" onRender={this.renderCallback}>
//... 需要被检测性能的结构
</Profiler>
renderCallback(...rest){
console.log(rest);
}
与第三方库协同
要确定在dom挂载完成再去操作
深入理解JSX
JSX类型不能使用表达式,处理方法,将组件在外面定义好组件,相当于Vue中的动态组件
表达式
true 在vue中是undifande
propTypes
进行props属性检查
需要先引入包,搭建脚手架的时候已经安装好了
import types from 'prop-types'
Model.propTypes/*固定字段*/={
/*效验属性类型*/
val:types.string, //string,
title:types.any.isRequired
/*写一个必填,数字组成的数组或者字符串组成的数组,其他的格式自定义效验方式的*/
test: types.arrayOf(types.oneOfType([
types.number,
types.string,
function(){
}
])).isRequired
}
在react中如何配置跨域?
creat-react-app 在react脚手架搭建中找解决方法
配置多个跨域:
src/setupProxy.js
const proxy = require('http-proxy-middleware');
module.exports = function(app) {
app.use(
'/ajax',
proxy({
target: 'http://m.maoyan.com',
changeOrigin: true
})
);
};
eject 执行
调出webpack的配置环境,就可以自己添加修改配置
webpack resolve.alias
可以设置别名
npm run eject
执行完成后,找到新增进来的webpack.config.js
....
return{
...
resolve:{
....
alias:{
{...},
...(modules.webpackAliases || {}),
pages: path.resolve(__dirname, '../src/pages'), //配置路径别名
}
}
}
配置完后即可使用
import Movie from 'pages/movie/movie/movie'