zoukankan      html  css  js  c++  java
  • 一、React基础

    起步

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

    2、创建项目:create-react-app react-project-name

    3、启动项目:npm start

    4、暴露配置项:npm run eject

    入口文件:index.js

    ReactDOM.render(<App />, document.getElementById('root'));

    babel-loader 最终会把jsx转换成js能识别的内容

    JSX:是一种JavaScript的语法扩展,其格式比较像模板语言,但事实上完全是在JavaScript内部实现的

    JSX实质就是 React.createElement 的调用,最终的结果是React元素(JavaScript对象)

    const jsx = <div>hello react</div>
    ReactDOM.render(jsx, document.getElementById('root'));

    组件

    组件是抽象的独立功能模块,react应用程序由组件构建而成

    组件的两种表现形式:class组件  function组件

    class组件通常拥有状态(state)和生命周期(lifeCycle),继承于Component,实现render方法

    import React, { Component } from 'react'
    
    export default class Home extends Component {
        render() {
            return (
                <div>
                    我是home页面
                </div>
            )
        }
    }

    function组件

    函数组件通常无状态,仅关注内容展示,返回渲染结果即可。

    ---从React16.8开始引入了hooks,函数组件也能够拥有状态。

    写一个定时器显示当前组件

    import React, { Component } from 'react'
    
    export default class Home extends Component {
        constructor(props){
            super(props)
            this.state = {
                date: new Date()
            }
        }
        // 组件挂载后---生命周期函数
        componentDidMount(){
            this.timerId = setInterval(()=>{
                this.setState({
                    date: new Date()
                })
            }, 1000)
        }
        // 组件卸载之前
        componentWillUnmount(){
            clearInterval(this.timerId)
        }
        render() {
            const str = '我是home页面'
            const {date} = this.state
            return (
                <div>
                    <h1>{str}</h1>
                    <p>{date.toLocaleTimeString()}</p>
                </div>
            )
        }
    }

    setState方法

    setCounter(){  // 这样写找不到this
        this.setState({
            counter: this.state.counter + 1
        })
    }
    
    // 解决方法一
    constructor(props){
        super(props)
        this.state = {
            date: new Date(),
            counter: 0
        }
        this.setCounter = this.setCounter.bind(this)
    }
    setCounter(){
        this.setState({
            counter: this.state.counter + 1
        })
    }
    
    // 解决方法二
    setCounter = () => {
        this.setState({
            counter: this.state.counter + 1
        })
    }

    setState为异步,如何将其实现同步

    setCounter = () => {
        this.setState({
            counter: this.state.counter + 1
        })
        console.log(this.state.counter) // 这里打印0,页面显示1
    }
    
    // 打印0时,页面显示2,并且每次只加2
    setCounter = () => {
        this.setState({
            counter: this.state.counter + 1
        })
        this.setState({
            counter: this.state.counter + 2
        })
        console.log(this.state.counter) 
    }
    
    // 实现同步
    // 方式一
    setCounter = () => {
        this.setState(nextState => {
            return {
                counter: nextState.counter + 1
            }
        })
        this.setState(nextState => {
            return {
                counter: nextState.counter + 2
            }
        })
    }
    
    // 方式二
    setCounter = () => {
        setTimeout(() => {
            this.setState({
                counter: this.state.counter + 1
            })
            this.setState({
                counter: this.state.counter + 2
            })
            console.log(this.state.counter)
        }, 0)
    }
    
    // 方式三:原生事件上去绑定
    componentDidMount(){
        document.getElementsByTagName('button')[0].addEventListener('click', ()=>{
            this.setState({
                counter: this.state.counter + 1
            })
            this.setState({
                counter: this.state.counter + 2
            })
        })
    }

     总结:setState只有在合成事件和钩子函数中是异步的,在原生事件和setTimeout、setInterval中都是同步的

    function组件

    function组件是没有状态和生命周期的

    如果一定要使用state,可以进行引入 useState、useEffect

    下列代码将上面的class组件转成funciton组件

    import React, {useState, useEffect} from 'react'
    
    export default function User() {
        const [date, setDate] = useState(new Date())
        useEffect(() => {
            const timerId = setInterval(() => {
                setDate(new Date())
            }, 1000)
            return () => clearInterval(timerId)
        })
        return (
            <div>
                <h1>我是user页面</h1>
                <p>{date.toLocaleTimeString()}</p>
            </div>
        )
    }

    事件处理

    onclick事件和onchange事件

    import React, { Component } from 'react'
    
    export default class Search extends Component {
        constructor(props){
            super(props)
            this.state = {
                name: '12'
            }
        }
        handler = () => {
            console.log('handler');
        }
        change = (event) => {
            let value = event.target.value
            this.setState({
                name: value
            })
            console.log('change', this.state.name);
        }
        render() {
            const {name} = this.state
            console.log(this);return (
                <div>
                    <h1>我是Search页面</h1>
                    <button onClick={this.handler}>click</button>
                    <input onChange={this.change} value={name}/>
                </div>
            )
        }
    }

    事件回调函数注意绑定this指向,常见三种方法:

    1、构造函数中绑定并覆盖:this.change = this.change.bind(this)

    2、方法定义为箭头函数:change = ()=>{}

    3、事件中定义为箭头函数:onChange = { () => this.change() } 

    --- react里遵循单项数据流,没有双向绑定,输入框要设置value和onChange,成为受控组件。

    组件通信

    props属性传递

    // App.js中
    const store = {
      userInfo: {
        userName: 'Dylan'
      }
    }
    function tellme(msg){
      console.log('tellme:', msg);
    }
    function App() {
      return (
        <div className="App">
          <Search store={store} tellme={tellme} />
        </div>
      );
    }
    
    export default App;
    
    // Search.js中
    handler = () => {
        console.log('handler');
        const {tellme} = this.props
        tellme('im dylanLv')
    }
    render() {
        const {name} = this.state
        console.log(this);
        const {userInfo} = this.props.store
        return (
            <div>
                <h1>我是Search页面,{userInfo.userName}</h1>
                <button onClick={this.handler}>click</button>
                <input onChange={this.change} value={name}/>
            </div>
        )
    }

    如果父组件传递的是函数,则可以把子组件信息传入父组件,这个常称为状态提示 

    react16的生命周期

    import React, {Component} from 'react'
    
    export default class LifeCycle extends Component {
      constructor(props) {
        super(props)
        this.state = {
          counter: 0
        }
        console.log('constructor', this.state.counter);
      }
      // 挂载之前
      UNSAFE_componentWillMount() {
        console.log('componentWillMount', this.state.counter);
      }
      // 挂载之后
      componentDidMount() {
        console.log('componentDidMount', this.state.counter);
      }
      // 更新之前
      UNSAFE_componentWillUpdate() {
        console.log('componentWillUpdate', this.state.counter);
      }
      // 更新之后
      componentDidUpdate() {
        console.log('componentDidUpdate', this.state.counter);
      }
      // 卸载之前
      componentWillUnmount() {
        console.log('componentWillUnmount', this.state.counter);
      }
      // 是否更新--执行render--优化使用
      shouldComponentUpdate(nextProps, nextState) {
        console.log('shouldComponentUpdate', this.state.counter, nextState.counter, nextProps);
        return this.state.counter != 5
      }
      setCounter = () => {
        this.setState({
          counter: this.state.counter + 1
        })
      }
    
      render() {
        console.log('render');
        const {counter} = this.state
        return (
          <div>
            <h1>我是一个lifeCycle, Hylan</h1>
            <p>{counter}</p>
            <button onClick={this.setCounter}>click</button>
            {!!(counter % 2) && <Foo/>}
          </div>
        )
      }
    }
    
    class Foo extends Component {
      componentWillUnmount() {
        console.log('componentWillUnmount   foo');
      }
      render() {
        return <div>我是FOO组件</div>
      }
    }

    V17可能会废弃的三个生命周期函数用 getDerivedStateFromProps 替代,目前使用的话加上 UNSAFE_

    • componentWillMount
    • componentWillReceiveProps
    • conponentWillUpdate

    引入两个新的生命周期

    • static getDerivedStateFromProps
    • getSnapshotBeforeUpdate
    static getDerivedStateFromProps(props, state){
      // getDerivedStateFromProps 会在调用render方法之前调用
      // 并且在初始挂载以及后续更新时都会被调用
      // 它应返回一个对象来更新 state ,如果返回 null 则不更新任何内容
      const { counter } = state
      console.log('getDerivedStateFromProps', counter)
      return counter < 8 ? null : { counter: 0 }
    }
    
    // 上一次(更新前)的影像
    // 返回值会作为 componentDidUpdate 的第三个参数
    getSnapshotBeforeUpdate(prevProps, prevState){
      const { counter } = prevState
      console.log('getSnapshotBeforeUpdate', counter)
      return null
    }
    // 更新之后
    componentDidUpdate(prevProps, prevState, snapshot) {
      console.log('componentDidUpdate', this.state.counter);
    }

    使用Context 上下文传值---可以跨层级传值

    // App.js
    import React from 'react';
    import './App.css';
    import Home from './pages/Home';
    
    const Context = React.createContext()
    const Provider = Context.Provider
    const Consumer = Context.Consumer
    
    const store = {
      home: {},
      user: {
        name: 'Dylan'
      }
    }
    
    function App() {
      return (
        <div className="App">
          <Provider value={store}>
            <Consumer>
              {ctx => <Home {...ctx}/>}
            </Consumer>
          </Provider>
    
        </div>
      );
    }
    
    export default App;
    
    // Home.js
    import React, { Component } from 'react'
    
    export default class Home extends Component {
        render() {
            // home:{
            //     home: {}
            //     user: {name: "Dylan"}
            // }
            console.log("home", this.props);
            return (
                <div>
                    home
                </div>
            )
        }
    }

    一般使用时会将Context暴露在外部,使用者将其引用就行了

     AppContext.js 文件

    import React from 'react';
    
    const Context = React.createContext()
    export const Provider = Context.Provider
    export const Consumer = Context.Consumer

    App.js 文件

    import React from 'react';
    import './App.css';
    import Home from './pages/Home';
    import {Provider, Consumer} from './AppContext'
    const store = {
      home: {},
      user: {
        name: 'Dylan'
      }
    }
    function App() {
      return (
        <div className="App">
          <Provider value={store}>
            <Consumer>
              {ctx => <Home {...ctx}/>}
            </Consumer>
          </Provider>
    
        </div>
      );
    }
    export default App;

    跨层级传值

    App.js 文件

    const store = {
      home: {},
      user: {
        name: 'Dylan'
      }
    }
    function App() {
      return (
        <div className="App">
          <Provider value={store}>
            <Home />
          </Provider>
    
        </div>
      );
    }

    Home.js文件

    import React, { Component } from 'react'
    import {Consumer} from '../AppContext'
    
    export default class Home extends Component {
        render() {
            console.log("home", this.props);
            return <Consumer>
                {ctx => <HomeHandle {...ctx}/>}
            </Consumer>
        }
    }
    
    function HomeHandle(props){
        console.log('HomeHandle', props);
        return <div>
            <h1>HomeHandle</h1>
        </div>
    }

    组件复合---Composition

    TabBar.js 文件

    import React, {Component} from 'react'
    
    export default class TabBar extends Component {
      render() {
        return <div className="tabBar">TabBar</div>
      }
    }

    Layout.js 文件

    import React, {Component} from 'react'
    import TabBar from '../components/TabBar'
    
    export default class Layout extends Component {
      render() {
        const {children, showTabBar = true} = this.props
        return (
          <div>
            {children}
            {showTabBar && <TabBar />}
          </div>
        )
      }
    }

    Home.js  文件 ,使用Layout模板

    import React, {Component} from 'react'
    import {Consumer} from '../AppContext'
    import Layout from './Layout';
    
    export default class Home extends Component {
      render() {
        return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
      }
    }
    
    function HomeHandle(props) {
      console.log('HomeHandle', props);
      return <Layout showTabBar={true}>
        <div>
          <h1>Home</h1>
        </div>
      </Layout>
    }

    上述类似  vue  中的匿名插槽

    下面代码展示具名插槽

    Home.js 文件

    import React, {Component} from 'react'
    import {Consumer} from '../AppContext'
    import Layout from './Layout';
    
    export default class Home extends Component {
      render() {
        return <Consumer>{ctx => <HomeHandle {...ctx}/>}</Consumer>
      }
    }
    
    function HomeHandle(props) {
      console.log('HomeHandle', props);
      return <Layout>
        {{
            btn: <button>按钮</button>,
            content: "我是内容"
        }}
        
      </Layout>
    }

    Layout.js  文件

    import React, {Component} from 'react'
    import TabBar from '../components/TabBar'
    
    export default class Layout extends Component {
        componentDidMount(){
            const { title='商场' } = this.props
            document.title = title
        }
      render() {
          const {children, showTabBar} = this.props
          console.log('props:',this.props);
        return (
          <div>
            {children.btn}
            {children.content}
            {showTabBar && <TabBar />}
          </div>
        )
      }
    }

    如何让layout自行判断传进来的组件是具名还是匿名

    Layout.js 文件

    render() {
        const {children, showTabBar = true} = this.props
        const a = []
        // 如果有这个值($$typeop) , 说明是匿名的
        if(children.$$typeof){
        a.push(children)
        }else{
            for(let item in children){
                a.push(children[item])
            }
        }
    return (
        <div>
        {
            a.map((item, index) => {
                return <div key={`child${index}`}>{item}</div>
            })
        }
        {showTabBar && <TabBar />}
        </div>
    )

    高阶组件

    将Home.js 进行修改

    import React, {Component} from 'react'
    import {Consumer} from '../AppContext'
    import Layout from './Layout';
    
    const consumerHandler = Cmp => props => {
      return <Consumer>
        {ctx => <Cmp {...props} {...ctx}/>}
      </Consumer>
    }
    
    function HomeHandle(props) {
      console.log('HomeHandle', props);
      return <Layout>
        <div>
          <h1>home</h1>
        </div>
      </Layout>
    }
    
    export default consumerHandler(HomeHandle)

    可以将其放入 AppContext.js 文件(公共文件)

    import React from 'react';
    
    const Context = React.createContext()
    export const Provider = Context.Provider
    export const Consumer = Context.Consumer
    
    export const consumerHandler = Cmp => props => {
      return <Consumer>
        {ctx => <Cmp {...props} {...ctx}/>}
      </Consumer>
    }

    高阶组件---HOC

    为了提高组件复用性,可测试性,就要保证组件功能单一性;但是若要满足复杂需求就要扩展功能单一的组件,在React里就有了HOC(Higher-Order Components)的概念。

    定义:高阶组件就是一个工厂函数,它接收一个组件并返回另一个组件。

    改写 App.js,展示高阶组件的用法

    function Child(props){
      return <div>child</div>
    }
    
    const foo = Cmp => props => {
      return <div style={{border: '1px solid black'}}>
        <Cmp {...props}/>
      </div>
    }
    
    
    function App() {
      const Foo = foo(Child)
      return (
        <div className="App">
          <Child />
          <Foo />
          {/* <Home /> */}
          {/* <User /> */}
        </div>
      );
    }
    export default App;

    高阶组件实际上就是,你给我一个组件,我把这个组件封装一下,再返回给你一个封装后的组件

    高阶组件可以链式调用

    function Child(props){
      return <div>child</div>
    }
    
    const foo = Cmp => props => {
      return <div style={{border: '1px solid black'}}>
        <Cmp {...props}/>
      </div>
    }
    
    const foo2 = Cmp => props => {
      return <div style={{border: '1px solid red', padding: '10px'}}>
        <Cmp {...props}/>
      </div>
    }
    
    function App() {
      const Foo = foo2(foo(Child))
      return (
        <div className="App">
          <Child />
          <Foo />
        </div>
      );
    }
    export default App;

    装饰器写法

    高阶组件本身是对装饰器模式的应用,自然可以利用ES7中出现的装饰器语法来更优雅的书写代码。

    CRA项目中默认不支持js代码使用装饰器语法,可修改后缀名为 tsx 则可以直接支持

    Hooks

    Hook是React16.8的一个新增项,它可以让你在不编写 class 的情况下使用 state 以及其他的 React 特性。

    Hooks的特点:

    • 是你在无需修改组件结构的情况下复用状态逻辑
    • 可将组件中相互关联的部分拆分成更小的函数,复杂组件将变得更容易理解
    • 更简洁、更易理解的代码

    hook中使用子组件,代码如下:

    import React, {useState, useEffect} from 'react'
    
    export default function HooksPage() {
      const [date, setDate] = useState(new Date())
      const [fruits, setFruits] = useState(['apple', 'banana'])
      useEffect(() => {
        const timerId = setInterval(() => {
          setDate(new Date())
        }, 1000)
        return () => clearInterval(timerId)
      })
      return (
        <div>
          hooks
          <p>{date.toLocaleTimeString()}</p>
                {/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
          <FruitList fruits={fruits} setFruits={setFruits}/>
        </div>
      )
    }
    
    function FruitList({fruits, setFruits}) {
        // 点击后删除当前项
        const delCur = index => {
            const tem = [...fruits]
            tem.splice(index, 1)
            setFruits(tem)
        }
      return <div>
        <ul>
          {
             fruits.map((item, index) => {
                return <li key={`fruit${index}`} onClick={()=>delCur(index)}>{item}</li>
             })
          }
        </ul>
      </div>
    }

    添加一个新增 fruits 操作

    export default function HooksPage() {
      const [date, setDate] = useState(new Date())
      const [fruits, setFruits] = useState(['apple', 'banana'])
      useEffect(() => {
        const timerId = setInterval(() => {
          setDate(new Date())
        }, 1000)
        return () => clearInterval(timerId)
        })
      return (
        <div>
          hooks
          <p>{date.toLocaleTimeString()}</p>
        {/* 传入addFruit函数 */}
         <AddFruit addFruit={item=>setFruits([...fruits, item])} />
        {/* 使用子组件时,需要把 fruits 和 setFruits 传入后,子组件内方可使用 */}
          <FruitList fruits={fruits} setFruits={setFruits}/>
        </div>
      )
    }
    
    function AddFruit({addFruit}) {
      const [name, setName] = useState("")
      return <div>
       <input value={name} onChange={e => setName(e.target.value)} />
       {/* 接收addFruit函数,点击按钮进行触发 */}
        <button onClick={()=>addFruit(name)}>点击增加</button>
      </div>
    }

    如果在 useEffect 中加入  console.log('useEffect'),可以发现不管是 setDate执行,还是点击新增按钮后的 setFruits 执行,console.log都会执行。

    但实际上当前功能只需要 date的定时器不断执行而已,可以修改代码进行优化

    useEffect(() => {
      console.log('useEffect');
      const timerId = setInterval(() => {
        setDate(new Date())
      }, 1000)
      return () => clearInterval(timerId)
      // useEffect的第二个参数,加入后就只对 date 生效了
    }, [date])

    接下来 点击 新增按钮 ,就不会打印 useEffect 了

    副作用钩子

    useEffect给函数组件增加了执行副作用操作的能力。

    副作用(Side Effect)是指一个function 做了和本身运算返回值无关的事,比如:修改了全局变量、修改了传入的参数、甚至是 console.log ,所以ajax操作,修改dom都算做副作用。

    • 异步数据获取,更新HooksTest.js
    useEffect(() => {
      setTimeout(()=>{
        setFruits(['香蕉','西瓜'])
      }, 1000)
    })

    测试就会发现副作用操作会被频繁调用

    • 设置依赖
    // 设置空数组意为没有依赖,则副作用操作仅执行一次
    useEffect(()=>{...}, [])
    • 清除工作:有一些副作用是需要清除的,清除工作非常重要,可以防止引起内存泄漏
    useEffect(() => {
      const timerId = setInterval(() => {
        console.log('mgs');
      }, 1000)
      return () => clearInterval(timerId)
    }, [])

    组件卸载后回执行返回的清理函数

    useReducer

    useReducer是useState的可选项,常用于组件有复杂状态逻辑时

    将上面的代码改成使用 useReducer

    Fruit.js 文件

    import React, {useState, useEffect} from 'react'
    
    export function AddFruit({addFruit}) {
      const [name,
        setName] = useState("")
      return <div>
        <input value={name} onChange={e => setName(e.target.value)}/> 
        <button onClick={() => addFruit(name)}>点击增加</button>
      </div>
    }
    
    // FruitList 组件需要传入 fruits 和 setFruits
    export function FruitList({fruits, setFruits}) {
      // 点击后删除当前项
      const delCur = index => {
        const tem = [...fruits]
        tem.splice(index, 1)
        setFruits(tem)
      }
      return <div>
        <ul>
          {
            fruits.map((item, index) => {
              return <li key={`fruit${index}`} onClick={() => delCur(index)}>{item}</li>
            })
          }
        </ul>
      </div>
    }

    HookReducer.js 文件

    import React, { useEffect, useReducer } from 'react'
    import { FruitList, AddFruit } from '../components/Fruit'
    
    function fruitsReducer(state, action){
      switch(action.type){
        case 'init':
        case 'replace':
          return action.paylod
        default:
          return state
      }
    }
    
    export default function HooksReducer() {
      const [fruits, dispatch] = useReducer(fruitsReducer, [])
      useEffect(()=>{
        setTimeout(()=>{
          dispatch({type: 'init', paylod: ['apple', 'banana']})
        }, 1000)
        return ()=>{
          // clearup
        }
      }, [])
      return (
        <div>
          HooksReducer
          <FruitList fruits={fruits} setFruits={(newFruitList)=>dispatch({type: 'replace', paylod: newFruitList})}/>
        </div>
      )
    }

    useContext

    useContext 用于 快速在函数组件中导入上下文。---之前是用Consumer做的

    import React, { useContext } from 'react'
    
    const Context = React.createContext()
    const Provider = Context.Provider
    
    export default function HooksContext() {
      const store = {
        user: {
          name: 'Dylan'
        }
      }
      return (
        <div>
          <Provider value={store}>
            <ContextChild></ContextChild>
          </Provider>
        </div>
      )
    }
    
    function ContextChild(props){
      console.log(useContext(Context));
      const {user} = useContext(Context)
      return <div>
        {user.name}
      </div>
    }

     

     
  • 相关阅读:
    梦断代码读后感02
    UML大战需求与分析--阅读笔记4
    UML大战需求分析--阅读笔记3
    软件需求与分析课堂讨论一
    UML大战需求分析--阅读笔记02
    UML大战需求分析--阅读笔记01
    学习进度--大三下
    问题账户需求分析
    2016年秋季阅读计划
    个人总结
  • 原文地址:https://www.cnblogs.com/haishen/p/11672813.html
Copyright © 2011-2022 走看看