坚持周总结系列第五周(react)

React

React项目起步

项目创建

  • 安装官方脚手架:npm install -g create-react-app

  • 创建项目:create-react-app react-study

  • 启动项目:npm start

  • 查看项目配置:npm run eject会弹出项目真面目,项目会多出两个目录

    • config
      • env.js处理.env环境变量配置文件
      • paths.js提供各种路径
      • webpack.config.js webpack配置文件
      • webpackDevserver.config.js测试服务器配置文件
    • scripts 启动、打包和测试脚本
      • build.js 打包脚本
      • start.js 启动脚本
      • test.js 测试脚本
  • 修改项目端口号

    创建.env文件

    PORT=8080
    

ReactReactDom

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>
    
  • JSXJS对象也是合法表达式

    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.childrenjsx,此时它是不能修改的
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('')
    }
  }
  // ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值