React
React项目起步
项目创建
-
安装官方脚手架:
npm install -g create-react-app -
创建项目:
create-react-app react-study -
启动项目:
npm start -
查看项目配置:
npm run eject会弹出项目真面目,项目会多出两个目录configenv.js处理.env环境变量配置文件paths.js提供各种路径webpack.config.jswebpack配置文件webpackDevserver.config.js测试服务器配置文件
scripts启动、打包和测试脚本build.js打包脚本start.js启动脚本test.js测试脚本
-
修改项目端口号
创建
.env文件PORT=8080
React和ReactDom
import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render(<h1>React真牛!</h1>,document.querySelector('#root'))
Ract负责逻辑控制,使用JSX描述UI,完成数据到VDOM的映射ReactDom负责渲染DOM,完成VDOM到真实DOM的渲染,如果是移动端,就换别的库来完成渲染
JSX
-
表达式**{…}**的使用
const name='react study' const jsx=<h2>{name}</h2> -
函数也是合法的表达式
const user={firstName:'tom',lastName:'jerry'} function formatName(user){ return user.firstName+' '+user.lastName } const jsx=<h2>{formatName(user)}</h2> -
JSX时JS对象也是合法表达式const greet=<p>hello,Jerry</p> const jsx=<h2>{greet}</h2> -
条件语句的实现
const showTitle=true const title=name?<h2>{name}</h2>:null const jsx=( <div> {/* 条件语句 */} {title} </div> ) -
数组存放一组
JSX用于显示列表数据const arr=[1,2,3].map(num=><li key={num}>{num}</li>) const jsx=( <div> {/* 数组 */} <ul>{arr}</ul> </div> ) -
属性的使用
import logo from './logo.svg' const jsx=( <div> {/* 属性:静态值用双引号,动态值用花括号;class、for等要特殊处理 */} <img src={logo} style={{width:100}} className="img" /> </div> ) -
css模块化import style from './index.module.css' <img className={style.img} />
组件
class组件
class组件通常拥有状态和生命周期,继承于Component,实现render方法
import React,{Component} from 'react'
import logo from '../logo.svg'
import style from '../index.module.css'
export default class JsxTest extends Component{
render(){
const name='react study'
const user={firstName:'tom',lastName:'jerry'}
function formatName(user){
return user.firstName+' '+user.lastName
}
const greet=<p>hello,jerry</p>
const arr=[1,2,3].map(num=><li key={num}>{num}</li>)
return (
<div>
{/* 条件语句 */}
{name ? <h2>{name}</h2> : null}
{/* 函数也是表达式 */}
{formatName(user)}
{/* jsx也是表达式 */}
{greet}
{/* 数组 */}
<ul>{arr}</ul>
{/* 属性 */}
<img src={logo} className={style.img} alt="" />
</div>
)
}
}
function组件
函数组件通常无状态,仅关注内容展示,返回渲染结果
import React from 'react'
import JsxTest from './component/JsxTest'
function App(){
return (
<div>
<JsxTest />
</div>
)
}
export default App
组件状态管理
-
类组件中的状态管理
class Clock extends Component { constructor (props) { super(props) // 使用state属性维护状态,在构造函数中初始化状态 this.state = { date: new Date() } } componentDidMount () { // 组件挂载时启动定时器每秒更新状态 this.timerID = setInterval(() => { // 使用setState方法更新状态 this.setState({ date: new Date() }) }, 1000) } componentWillUnmount () { // 组件卸载时停止定时器 clearInterval(this.timerID) } render () { return <div>{this.state.date.toLocaleTimeString()}</div> } } -
state中的状态要使用
setState更新而不能直接修改 -
setState是批量执行的,因此对同一个状态多次执行更新只有一次会起作用 -
setState是异步的,因此要立即获取最新状态,有三种方式-
传递函数给
setSate方法this.setSate((state,props)=>({counter:satte.counter+1})) -
使用定时器
setTimeout(()=>{ console.log(this.state.counter) },0) -
原生事件中修改状态
componentDidMount(){ document.body.addEventListener('click',this.changeValue,false) } changeValue=()=>{ this.setState({counter:this.state.counter+1}) console.log(this.state.counter) }
-
-
函数组件中的状态管理
function ClockFUnc () { // useState创建一个状态和修改该状态的函数 const [date, setDate] = useState(new Date()) // useEffect编写副作用代码 useEffect(() => { // 启动定时器就是我们的副作用 const timerID = setInterval(() => { setDate(new Date()) }, 1000) // 返回清理函数 return () => clearInterval(timerID) }, []) // 传递空数组,就是没有任何依赖,副作用函数只会执行一次 return <div>{date.toLocaleTimeString()}</div> }
事件处理
export default class EventHandle extends Component {
constructor (props) {
super(props)
this.state = {
name: ''
}
this.handleChange = this.handleChange.bind(this)
}
handleChange (e) {
this.setState({ name: e.target.value })
}
render () {
return (
<div>
{/* 箭头函数,不足要指定this,且便于传参 */}
{/* <input
type='text'
value={this.state.name}
onChange={e => this.handleChange(e)}
/> */}
{/* 直接指定回调函数,需要指定其this指向,或者将回调设置为箭头函数属性 */}
<input
type='text'
value={this.state.name}
onChange={this.handleChange}
/>
</div>
)
}
}
组件通信
props属性传递
props属性传递可以用于父子组件相互通信
ReactDOM.reander(<APP title='开课吧' />,document.querySelector('#root'))
// App.js
<h2>{this.props.title}</h2>
如果父组件传递的是函数,则可以把子组件信息传入父组件,这个通常称为状态提升
<Clock change={()=>this.onChange()} />
onChange(date){
console.log(date.toLocaleTimeString())
}
// Clock.js
this.timerID=setInterVal(()=>{
this.setState({date:new Date()},()=>{
// 每次状态更新就通知父组件
this.props.change(this.state.date)
})
},1000)
Context
跨层级组件之间通信
redux
类似vuex,无明显关系组件之间通信
组件声明周期
export default class Lifecycle extends Component {
constructor (props) {
super(props)
// 通常用语初始化状态
console.log('1.组件构造函数执行')
}
componentWillMount () {
// 此时可以访问组件状态和属性,可进行api调用等
console.log('2.组件将要挂载')
}
componentDidMount () {
// 组件已经挂载,可进行状态更新操作
console.log('3.组件已经挂载')
}
componentWillReceiveProps (nextProps, nextState) {
// 父组件传递的属性有变化,作出相应响应
console.log('4.将要接收属性传递')
}
shouldComponentUpdate (nextProps, nextState) {
// 组件是否需要更新,需要返回布尔值,优化点
console.log('5.组件是否需要更新?')
return true
}
componentWillUpdate () {
// 组件将要更新
console.log('6.组件将要更新')
}
componentDidUpdate () {
// 组件更新
console.log('7.组将已更新')
}
componentWillUnmount () {
// 组件将要卸载,可做清理工作
console.log('8.组将将要卸载')
}
render () {
console.log('组件渲染')
return <div>组件声明周期</div>
}
}
组件化
组件跨层级通信
import React from 'react'
// 创建上下文
const Context = React.createContext()
// 获取Provider和Consumer
const Provider = Context.Provider
const Consumer = Context.Consumer
// Child显示计数器,并能修改它,多个Child之间需要共享状态
function Child (props) {
return <div onClick={() => props.add()}>{props.counter}</div>
}
export default class ContextTest extends React.Component {
// state是要传递的数据
state = { counter: 0 }
// add方法可以修改状态
add () {
this.setState(nextState => ({ counter: nextState.counter + 1 }))
}
render () {
return (
<Provider value={{ counter: this.state.counter, add: this.add }}>
{/* Consumer中内嵌函数,其参数是传递的数据,返回要渲染的组件 */}
{/* 把value展开传递给child */}
<Consumer>{value => <Child {...value} />}</Consumer>
<Consumer>{value => <Child {...value} />}</Consumer>
</Provider>
)
}
}
高阶组件
import React from 'react'
// Lesson保证功能单一,他不关心数据来源,只负责显示
function Lesson (props) {
return (
<div>
{props.stage}-{props.title}
</div>
)
}
// 模拟数据
const lessons = [
{ stage: 'React', title: '核心API' },
{ stage: 'React', title: '组件化1' },
{ stage: 'React', title: '组件化2' }
]
// 高阶组件withContent负责包装传入组件Comp
// 包装后组件能够根据索引获取课程数据,真实案例中可以通过api查询得到
const withContent = Comp => props => {
const content = lessons[props.id]
// {...props}将属性展开传递下去
return <Comp {...content} />
}
// lessonWithContent是包装后的组件
const LessonWithContent = withContent(Lesson)
export default function HocTest () {
return (
<div>
{[0, 0, 0].map((item, id) => (
<LessonWithContent id={id} key={id} />
))}
</div>
)
}
function withConsumer (Consumer) {
return Comp => props => {
return <Consumer>{value => <Comp {...value} {...props} />}</Consumer>
}
}
const Child = withConsumer(Consumer)(function (props) {
return (
<div onClick={() => props.add()} title={props.name}>
{props.counter}
</div>
)
})
export default class ContextTest extends Component {
render () {
return (
<Provider value={{ counter: this.state.counter, add: this.add }}>
<Child name='foo' />
<Child name='bar' />
</Provider>
)
}
}
- 链式调用
const withLog = Comp => {
return class extends Component {
render () {
return <Comp {...this.props} />
}
componentDidMount () {
console.log('didMount', this.props)
}
}
}
// lessonWithContent是包装后的组件
const LessonWithContent = withLog(withContent(Lesson))
组件复合
// Dialog定义组件外观和行为
function Dialog (props) {
return <div style={{ border: '1px solid blue' }}>{props.children}</div>
}
export default function Composition () {
return (
<div>
{/* 显示传入内容 */}
<Dialog>
<h1>组件复合</h1>
</Dialog>
</div>
)
}
- 传入对象,key表示具名插槽
import React from 'react'
// 获取相应部分内容展示在指定位置
function Dialog (props) {
return (
<div style={{ border: '1px solid blue' }}>
{props.children.default}
<div>{props.children.footer}</div>
</div>
)
}
export default function Composition () {
return (
<div>
{/* 传入现实内容 */}
<Dialog>
{{
default: (
<>
<h1>组件复合</h1>
</>
),
footer: <button onClick={() => alert('react')}>确定</button>
}}
</Dialog>
</div>
)
}
- 传入函数,实现作用域插槽
import React from 'react'
function Dialog (props) {
// 备选消息
messages = {
foo: { title: 'foo', content: 'foo~' },
bar: { title: 'bar', content: 'bar~' }
}
// 执行函数获得要显示的内容
const { body, footer } = props.children
return (
<div style={{ border: '1px solid blue' }}>
{/* 此处显示的内容是动态生成的 */}
{body}
<div>{footer}</div>
</div>
)
}
export default function Composition () {
return (
<div>
{/* 执行消息显示的key */}
<Dialog msg='foo'>
{/* 修改为函数形式,根据传入的值生成最终内容 */}
{({ title, content }) => ({
body: (
<>
<h1>{title}</h1>
<p>{content}</p>
</>
),
footer: <button onClick={() => alert('react')}>确定</button>
})}
</Dialog>
</div>
)
}
- 如果
props.children是jsx,此时它是不能修改的
import React from 'react'
function RadioGroup (props) {
// 不可行
// React.Children.forEach(props.children, child => {
// child.props.name = props.name
// })
return (
<div>
{React.Children.map(props.children, child => {
// 要修改child属性必须先克隆它
return React.cloneElement(child, { name: props.name })
})}
</div>
)
}
function Radio ({ children, ...rest }) {
return (
<label>
<input type='radio' {...rest} />
{children}
</label>
)
}
export default function Composition () {
return (
<div>
{/* 执行显示消息的key */}
<RadioGroup name='mvvm'>
<Radio value='vue'>vue</Radio>
<Radio value='react'>react</Radio>
<Radio value='ng'>angular</Radio>
</RadioGroup>
</div>
)
}
Hooks
状态钩子
const [fruit, setFruit] = useState('')
const [fruits, setFruits] = useState(['香蕉', '西瓜'])
副作用钩子
useEffect(()=>{
setTimeout(()=>{
setFruits(['西瓜','香蕉'])
},1000)
},[])
useReducer
import React, { useReducer, useEffect } from 'react'
// 添加reducer
function fruitReducer (state, action) {
switch (action.type) {
case 'init':
return action.payload
case 'add':
return [...state, action.payload]
default:
return state
}
}
export default function HookTest () {
const [fruits, dispatch] = useReducer(fruitReducer, [])
useEffect(() => {
dispatch({ type: 'init', payload: ['香蕉', '西瓜'] })
}, [])
return (
<div>
{/* 派发动作 */}
<FruitAdd
onAddFruit={pname => dispatch({ type: 'add', payload: pname })}
/>
</div>
)
}
useContext
import React, { useContext } from 'react'
// 创建上下文
const Context = React.createContext()
export default function HookTest () {
return (
// 提供上下文的值
<Context.Provider>
<div>
{/* 这里不再需要给组件传值 */}
<FruitAdd />
</div>
</Context.Provider>
)
}
function FruitAdd (props) {
// 使用useContext获取上下文内容
const { dispatch } = useContext(Context)
const onAddFruit = e => {
if (e.key === 'Enter') {
// 直接派发动作
dispatch({ type: 'add', payload: pname })
setPname('')
}
}
// ...
}

被折叠的 条评论
为什么被折叠?



