以下内容,当具备ES6,JS语法,node环境,前端组件基础概念,写过java代码,包你3天上手React项目,下面开始…
demo地址
https://gitee.com/tonysb/lear…
项目结构如下,Button的方式值得借鉴
react介绍
- react: 一个js框架,让开发者可以在js中写html代码,也就是jsx语法,称为虚拟dom(类似一个js对象)
- react-dom: 挂载节点,将jsx写的虚拟dom变成真的dom
- render: 每次都是新旧虚拟dom之间进行比较,之后才会生成真实dom
创建react项目
命令行使用 npx create-react-app my-app-name 即可创建项目
react中组件(子父之间传值)
A: 父组件 B: 子组件
在A.js文件中使用<B name={name} getName={this.getName}>
其中name为A文件中一个变量,getName为A文件中一个方法
在B.js文件中,可直接使用this.props.name
或this.props.getName
或const { name, getName } = this.props
来得到A中变量,或运行A中方法,最后一种最常用,这种方式是ES6中新增的解构赋值.
父->子 通过子标签上加上属性的方式,直接传递,在子重使用this.props来接住属性
子->父 通过在子中调用父传递的方法来完成
具体使用场景: 一个页面右上角挂载一个三级联动选项卡(三级数据从接口获取),main作为父组件主页面,select作为子组件三级选项卡页面
- main页面负责ajax请求,拿总数据,为子组件准备好所有即插即用数据和方法,在使用select标签的时候,全部传递给子
- select页面,在this.props中负责解构所有数据和方法,直接使用,无需关心逻辑实现
- main叫逻辑组件(聪明组件), select叫UI组件(笨蛋组件)
JSX语法
- render中return中的代码都是JSX,和html代码相似.
- 在js中直接写html语法,也可以使用自定义标签,比如可以写自己组件,App组件可以写成 <App />,首字母必须大写开头,JSX标签中,大写开头,基本都是组件,小写开头基本是html标签.
- render中用JSX语法写html代码,必须在最外层包括一个div,不然编译会报错,在16版本中,如果使用Fragment来表示占位符,在html中显示的时候,可去掉组件最外层的那个div,demo如下
<React.Fragment>
// 你的jsx代码
</React.Fragment>
- 使用js表达式/js变量需要使用{}把表达式包裹起来,demo如下
// ES6 ``,${}加三目运算符可处理复杂的css名字
<div className={`classA classB ${this.state.isSelect ? 'selected' : ''}`} />
// for循环遍历请使用map方法,sortTableRows是state中一个变量
{sortTableRows && sortTableRows.map(item => (
<div key={item.id} className="table-row">
<span>{item.title}</span>
<span>{item.author}</span>
<span>{item.comments}</span>
<span>{item.points}</span>
<span>
<CancelButton onClick={() => onMiss(item.id)}>miss</CancelButton>
</span>
</div>
))}
- html标签中进行事件绑定,事件名称首字母必须大写,比如 onChange中C就是大写的
- 在html样式中,使用className来代替class
- 使用dangerouslySetInnerHTML={{__html: item}},可在提交数据的时候,对数据中html标签进行转义处理
- <label for=”insertArea”>输入内容</label> for需要换成htmlfor
事件绑定
jsx合成事件绑定,js高阶函数,绑定的是函数,不能让函数马上执行
不带参数绑定,后面不能加(), 如果加,那函数会马上执行,而不是事件发生时候回调执行
带参数绑定,需要在一个匿名函数中写绑定函数
// 合成事件绑定,不带参数
<li onClick={this.props.delItem}>{item}</li>
// 合成事件绑定,带参数
<li onClick={(index) => this.props.delItem(index)}>{item}</li>
表单交互(input)
1, state中设置searchTerm变量,用来填充input中value值
2, 绑定input中onChange事件,获取event对象中value值,将该值set到searchTerm变量中
state和props和render
state: 组件自己内部维护数据,每次更新只需要关系需要更改的数据
1, set数据不依赖之前的数据,直接调用setState
this.setState({
foo: bar
})
2, set数据依赖之前数据,一定要使用带参数调用setState
this.setState((prevState, props) => (
// 你的代码
))
// bad
// setState是异步,代码中多个set,有可能fooCount和barCount已经被改变
const { fooCount } = this.state
const { barCount } = this.props
this.setState({ count: fooCount + barCount })
// good
this.setState((prevState, props) => (
count: prevState.fooCount + props.barCount
))
props: 父->子,所有传递过来的数据都附加在props中,子在props中可以拿到所有父传递过来的数据
render: 只要state进行set操作,render一定会执行,包括父state进行set之后,父和子的render都会被执行
ajax接参数
- 子组件是UI组件,接完父组件数据直接使用,可直接在子组件props中直接使用父组件ajax请求获取到的数据
- 子组件需要将父组件ajax数据赋值到自己的state中,只能在componentWillReceiveProps生命周期中处理,不能在constructor中处理,原因: 父组件ajax请求最起码第二次render父组件,而子组件中constructor只会执行一次
上述方式处理方式比较麻烦,推荐使用方式3
3, 父组件负责提供数据和处理处理的函数,子组件只负责获取数据,渲染页面,子组件事件交互,也是通过props来调用父组件中的函数(常用)
生命周期(重要)
以下是使用场景和注意点
组件挂载阶段(只会执行一次,render除外)
constructor(): 设置state,获取父组件props,并初始化自己的state,并绑定函数中this指向
componentWillMount(): 不能获取页面dom元素,目前还没用过
render():渲染页面,不做运算,到这里为止,所有数据都应该是经过处理可直接使用的数据
componentDidMount(): 可获取dom元素,ajax请求放这里,调用setState,设置dom元素监听,绘制canvas
组件更新阶段(state或props发生变化)
componentWillReceiveProps(nextProps): nextProps为更新新属性,可进行新旧属性对比,也可以根据新数据来计算和设置自己的state
shouldComponentUpdate(nextProps, nextState): 返回布尔值,可做渲染优化,根据场景决定是否渲染该组件,不要调用setState
componentDidUpdate(prevProps, prevState): 操作 DOM 或者执行更多异步请求的机会
componentWillUnmount: 取消网络请求,删除监听,一些收尾工作,不要调用setState
react中this指向
- 自定义函数的时候,写自定义函数,需要在constructor中进行函数this指向的绑定
- 在constructor中进行this指向绑定,然后写正常函数
// 方式1
getTodoItem = () => {
// 你的逻辑
}
// 方式2
constructor(props) {
super(props)
this.inputOnChange = this.inputOnChange.bind(this)
}
inputOnChange() {
// 你的逻辑
}
组件类型
ES6类组件: 有this,props,有state,有生命周期,根据设计图,一般当作父组件使用,从接口拿数据,计算成品数据,编写事件函数来处理数据
无状态组件(纯函数): 接收输入(props),输出jsx组件实例,没有this,没有生命周期,没有state,可根据props中的数据来计算符合自己要求的数据(组件中直接调用方法),
props中可以传递数据,也可以传递函数,一般当做子组件使用,demo如下
import React from 'react'
/*查询表单组件
* Search在使用的时候,标签中间就是children
* <Search value={searchTerm} onSubmit={this.onSubmit} onChange={this.onChange}>查询</Search> children的值就是查询
*/
const Search = ({value, onChange, onSubmit, children}) => {
return (
<div>
<form onSubmit={onSubmit}>
<input type='text' value={value} onChange={onChange}/>
<button type='submit'>{children}</button>
</form>
</div>
)
}
export default Search
import React from 'react'
import {Slider} from "antd"
import moment from "moment"
import './style.less'
/*
* 时间轴无状态组件
* timeData: 时间数组列表
* timeChange: 时间轴change事件的回调
* worktime: 当前选中时间
* */
const TimeSlider = ({timeData, timeChange, worktime}) => {
/*
* 获取时间轴当前选中值
* */
const getDefaultValue = () => {
const { id } = times.find(item => {
return item.dateTime === worktime
})
return id
}
/*
* 获取时间轴的总长度
* */
const getMax = () => {
return timeData.length
}
/*
* 获取时间轴显示的time数据
* 格式:
* {
* 0: '2019-11-11'
* 24: '2019-11-12'
* 23: '2019-11-13'
* }
* */
let times = []
const getMarks = () => {
let dates = []
console.log('timeData len', timeData.length)
timeData.map((item, index) => {
const time = {id: index, dateTime: item}
times.push(time)
dates.push(moment(item).format('YYYY-MM-DD'))
})
const newDates = [...new Set(dates)]
console.log('newDates', newDates)
let marks = {}
let datesAmount = []
newDates.map(item => {
const amount = getDateAmount(item, timeData)
const temp = {date: item, amount: amount}
datesAmount.push(temp)
})
for (let i = 0,length = datesAmount.length; i < length; i++) {
const date = {
style: {
color: '#fff',
marginLeft: 0
},
label: moment(datesAmount[i].date).format('MM/DD'),
}
if (i === 0) {
//const mark = JSON.parse(`{"0":"${date}"}`)
const mark = {0: date}
Object.assign(marks, mark)
} else {
const index = i - 1
const lastAmount = getLastDateAmount(index, datesAmount)
// const mark = JSON.parse(`{"${lastAmount}":"${date}"}`)
const mark = { [lastAmount]: date }
Object.assign(marks, mark)
}
}
return marks
}
/*
* 根据 2019-11-11来获取 数据 ['2019-11-11 11:11:11'] 对应的小时数量
* */
const getDateAmount = (date, timeData) => {
let amount = 0
for (let i = 0,length = timeData.length; i < length; i++ ) {
const item = timeData[i]
if (item.includes(date)) {
amount = amount + 1
}
}
return amount
}
/*
* 获取当前index往前推一个的所有小时数量
* */
const getLastDateAmount = (index, datesAmount) => {
let sum = 0
for (let i = 0; i <= index; i++) {
sum = sum + datesAmount[i].amount
}
return sum
}
const formatter = (value) => {
const time = times.find(item => {
return item.id === value
})
return moment(time.dateTime).format('HH:mm')
}
const onTimeChange = (value) => {
const time = times.find(item => {
return item.id === value
})
timeChange(time.dateTime)
}
return (
<div className='time-slider'>
<Slider max={getMax()} marks={getMarks()} defaultValue={getDefaultValue()} value={getDefaultValue()} tipFormatter={formatter} onChange={onTimeChange}/>
</div>
)
}
export default TimeSlider
顶层API
React.createRef
作用: 在当前组件中,创建dom,获取dom
class createRefDemo extends Component {
constructor(props) {
super(props)
this.myInput = React.createRef()
}
handleInputChange = () => {
const value = this.myInput.current.value // 这里通过current可获取input的dom对象
}
render() {
return (
<input id="hello" className='input' type="text" value='hello'
onChange={this.handleInputChange} ref={this.myInput}/>
)
}
}
React.forwardRef
参考链接: https://juejin.im/post/5c0dd4…
作用: 将refDom可以传递给子孙组件,永远传递下去,在HOC中常用
ReactDOM.createPortal
作用: 脱离父组件,将该组件挂载到dom的任何位置
// 将selectComp组件挂载到bodyDom下面
const selectComp = <Selectcj />
ReactDOM.createPortal(
selectComp,
document.querySelector('body'),
)
React.createContext
Provider和Consumer
两款chrome插件
- React Developer Tools // F12,查看每个react组件中state和props的值
- Redux DevTools // F12,查看redux中store的值