ReactJS语法-React 组件

函数组件

  1. 在React中,构造函数,就是一个最基本的组件
  2. 如果想要把组件放到页面中,可以把 构造函数的名称,当作 组件的名称,以 HTML标签形式引入页面中即可
  3. 注意: React在解析所有的标签的时候,是以标签的首字母来区分的,如果标签的首字母是小写,那么就按照 普通的 HTML 标签来解析,如果 首字母是大写,则按照 组件的形式去解析渲染,即组件的首字母必须是大写
  4. 特点:
    1. 没有生命周期函数
    2. this关键字不能指向组件实例
    3. 没有内部状态(state)
//1. 定义组件Hello,首字母必须大写
function Hello() {
    return <div>
        <h1>这是在Hello组件中定义的元素</h1>
        </div>
}
    
//2. 渲染组件
ReactDOM.render(<div>
    <Hello></Hello>
</div>,document.getElementById('app'))

组件传值

//1. 定义组件Hello,首字母必须大写
function Hello(props) {
    // 在组件中,如果想要使用外部传递过来的数据,必须显示的在 构造函数参数列表中,定义 props 属性来接收;
    // 通过 props 得到的任何数据都是只读的,不能重新赋值
    // props.name = '000'
    return <div>
        <h1>这是在Hello组件中定义的元素---{props.name}</h1>
    </div>
}
    
// var name = 'zs'
// var age = 20
var person = {
  name: 'ls',
  age: 22,
  gender: '男',
  address: '北京'
}
//2. 渲染组件
ReactDOM.render(<div>
    {//<Hello name={person.name} age={person.age} gender={person.gender} address={person.address}></Hello>}
    {//简要写法}
    <Hello {...person}></Hello>
</div>,document.getElementById('app'))
// 注意:这里 ...Obj  语法,是 ES6中的属性扩散, 表示把这个对象上的所有属性,展开了,放到这个位置

jsx文件

  1. 将上面封装的Hello组件,单独放到一个Hello.jsx文件中

     import React from 'react'
     function Hello(props) {
       return <div>
         <h1>这是在Hello组件中定义的元素 --- {props.name}</h1>
         <p id="">哈哈哈哈</p>
       </div>
     }
        
     // 把创建的组件暴露出去
     export default Hello
    
  2. main.js中引用

     import Hello from './components/Hello.jsx'
    

class创建组件

  1. 组件的首字母必须是大写
  2. 使用 class 创建的类,通过 extends 关键字,继承React.Component,这个类,就是一个组件的模板
  3. 在 class 实现的组件内部,必须定义一个 render 函数
  4. 在 render 函数中,返回的是JSX内容,如果没有什么需要被return 的,则需要 return null。
  5. 如果想要引用这个组件,可以把类的名称以标签形式,导入到JSX中使用
  6. 创建一个Hello2组件

     import React from 'react'
     class Hello2 extends React.Component {
         // 必须定义render函数
         render() {
             // 在 render 函数中,还必须 return 一个东西,如果没有什么需要被return 的,则需要 return null
             return <div>
               <h1>这是 使用 class 类创建的组件</h1>
             </div>
         }
     }
    

    组件中的数据

  7. 在组件中的数据,可以分成两类:
    1. 参与界面更新的数据:当数据变化时,需要更新组件染的内容,
    2. 不参与界面更新的数据:当数据变化时,不需要更新将组建渲染的内容;
  8. 参与界面更新的数据我们也可以称之为是参与数据流,这个数据是定义在当前对象的state中
    1. 可以通过在构造函数中 this.state ={定义的数据}
    2. 当我们的数据发生变化时,可以调用 this.setState 来更新数据,并且通知React进行update操作;
    3. 在进行update操作时,就会重新调用render函数,并且使用最新的数据,来渲染界面
    4. 注意:this.setState方法是异步的。并不能在执行完setState之后立马拿到最新的state的结果

组件数据传值

class Hello2 extends React.Component {
    // 在 constructor 中,如果想要访问 props 属性,不能直接使用 this.props, 而是需要在 constructor 的构造器参数列表中,显示的定义 props 参数来接收,才能正常使用;
    constructor(props) {
        // 注意: 如果使用 extends 实现了继承,那么在 constructor 的第一行,一定要显示调用一下 super(),super() 表示父类的构造函数
        super(props)
        console.log(props)
        
        // 注意: 这是固定写法,this.state 表示 当前组件实例的私有数据对象
        // 如果想要使用 组件中 state 上的数据,直接通过 this.state.*** 来访问即可
        this.state = {
          msg: '这是 Hello2 组件的私有msg数据',
          info: '瓦塔西***'
        }
    }
    render() {
        // 虽然在 React dev tools 中,并没有显示说 class 组件中的 props 是只读的,但是,经过测试得知,其实 只要是 组件的 props,都是只读的;
        // this.props.address = '123'

        return <div>
          <h1>这是 使用 class 类创建的组件</h1>
          <h3>外界传递过来的数据是: {this.props.address} --- {this.props.info}</h3>
          <h5>{this.state.msg}</h5>
        </div>
    }
    
}

ReactDOM.render(<div>
  <Hello2 address="xxx" info="XXX程序员"></Hello2>
</div>, document.getElementById('app'))

子组件给父组件传递数据

  1. 在React中是通过props传递消息,只是让父组件给子组件传递一个回调函数,在子组件中调用这个函数即可。
//父组件给子组件传递一个函数,用来接收子组件的数据,并渲染页面
export class App extends Component { 
    constructor(){
        super()
        this.state ={
            counter: 180
        }
    }
    changecounter(count){
        this.setstate({ counter:this.state.counter + count})
    }
    render(){
        const{ counter }= this.state
        return(
            <div>
                <h2>当前计数:{counter}</h2>
                <Addcounter addclick={(count)=> this.changecounter(count)}/>
            </div>  
        )
    }
}

//子组件点击事件变更数据,调用父组件传递过来的函数,将参数传递给父组件
export class Addcounter extendsComponent {
    addcount(count) {
        console.log("count:",count)
        this.props.addclick(count)
    }
    render(){
    return(
        <div>
            <button onclick={e => this.addcount(1)}>+1</button>
        </div>
        )
    }
}

访问组件的私有数据

class Hello2 extends React.Component {
    constructor(props) {
        super(props)
        this.state = {
          msg: '这是 Hello2 组件的私有msg数据',
          info: '瓦塔西***'
        }
    }
    
    render() {
        return <div>
          <h1>这是 使用 class 类创建的组件</h1>
          <h3>外界传递过来的数据是: {this.props.address} --- {this.props.info}</h3>
          //访问私有数据
          <h5>{this.state.msg}</h5>
          
          //修改组件私有数据
          {/* 1.1 在React中,如果想要为元素绑定事件,不能使用 网页中 传统的 onclick 事件,而是需要 使用 React 提供的  onClick */}
          {/* 1.2 也就是说:React中,提供的事件绑定机制,使用的 都是驼峰命名,同时,基本上传统的 JS 事件,都被 React 重新定义了一下,改成了 驼峰命名 onMouseMove  */}
          {/* 2.1 在 React 提供的事件绑定机制中,事件的处理函数,必须直接给定一个 function,而不是给定一个 function 的名称 */}
          {/* 2.2 在为 React 事件绑定 处理函数的时候,需要通过 this.函数名, 来把 函数的引用交给 事件 */}
          <input type="button" value="修改 msg" id="btnChangeMsg" onClick={this.changeMsg} />
          <br />

        </div>
    }
    
    changeMsg = () => {

    // 直接使用 this.state.msg = '123' 为 state 上的数据重新赋值,可以修改 state 中的数据值,但是,页面不会被更新;
    // 所以这种方式,React 不推荐,以后尽量少用;
    // this.state.msg = '123'

    // 如果要为 this.state 上的数据重新赋值,那么,React 推荐使用 this.setState({配置对象}) 来重新为 state 赋值,一旦数据发生改变页面会自动刷新!!!!!
    // 注意: this.setState 方法,只会重新覆盖那些 显示定义的属性值,如果没有提供最全的属性,则没有提供的属性值,不会被覆盖(info属性);
    /* this.setState({
      msg: '123'
    }) */

    // this.setState 方法,也支持传递一个 function,如果传递的是 function,则在 function 内部,必须return 一个 对象;
    // 在 function 的参数中,支持传递两个参数,其中,第一个参数是 prevState,表示为修改之前的 老的 state 数据
    // 第二个参数,是 外界传递给当前组件的 props 数据
    this.setState(function (prevState, props) {
      // console.log(props)
      return {
        msg: '123'
      }
      
    }, function () {
        //callback:回调函数
      // 由于 this.setState 是异步执行的,所以,如果想要立即拿到最新的修改结果,最保险的方式, 在回调函数中去操作最新的数据
      console.log(this.state.msg)
    })
    // 经过测试发现, this.setState 在调用的时候,内部是异步执行的,所以,当立即调用完 this.setState 后,输出 state 值可能是旧的
    // console.log(this.state.msg)
  }
}

Hello2单独创建jsx组件

  1. 将以上的Hello2组件单独封装到Hello2.jsx如下:

     import React from 'react'
     export default class Hello2 extends React.Component {
         //....不变
     }
    
  2. main.js导入

     // 在使用 Hello 组件之前,先导入 组件
     import Hello2 from './components/Hello2.jsx'
    

两种创建组件方式比较

  1. 使用 function 构造函数创建的组件,内部没有 state 私有数据,只有 一个 props 来接收外界传递过来的数据;
  2. 使用 class 关键字 创建的组件,内部,除了有 this.props 这个只读属性之外,还有一个 专门用于 存放自己私有数据的 this.state 属性,这个 state 是可读可写的!
  3. 使用 function 创建的组件,叫做无状态组件;使用 class 创建的组件,叫做有状态组件
  4. 有状态组件和无状态组件,最本质的区别,就是有无 state 属性;同时, class 创建的组件,有自己的生命周期函数,但是,function 创建的 组件,没有自己的生命周期函数;
  5. 什么时候使用 有状态组件,什么时候使用无状态组件呢?
    1. 如果一个组件需要存放自己的私有数据,或者需要在组件的不同阶段执行不同的业务逻辑,此时,非常适合用 class 创建出来的有状态组件;
    2. 如果一个组件,只需要根据外界传递过来的 props,渲染固定的 页面结构就完事儿了,此时,非常适合使用 function 创建出来的 无状态组件;(使用无状态组件的小小好处: 由于剔除了组件的生命周期,所以,运行速度会相对快一些)

props&state

  1. props属性只读,不能修改
  2. state为组件私有数据属性,一旦通过this.setState({配置对象})设置,自动刷新页面.

React组件的插槽

  1. 在开发中,我们抽取了一个组件,但是为了让这个组件具备更强的通用性,我们不能将组件中的内容限制为固定的div、span等等这些元素。
  2. React对于这种需要插槽的情况非常灵活,有两种方案可以实现:
    1. 组件的children子元素;
    2. props属性传递React元素;

children子元素实现插槽

export class App extends Component {
    render(){
        return(
            <div>
                {/*给导航栏组件传递子元素*/}
                <NavBar>
                <button>按钮</button>
                <h2>我是标题</h2>
                <i>斜体文字</i>
                </NavBar>
            </div>
        )
    }
}
//通用导航栏组件
export class NavBar extends Component {
    render(){
        //获取到NavBar组件的子元素
        //注意!!!:如果外部传递给NavBar的子元素只有一个,那么这个children就不是素组了,而是这个唯一的子元素
        const {children }= this.props
        return(
            <div className='nav-bar'>
                <div className="left">{children[0]}</div>
                <div className="center">{children[1]}</div>
                <div className="right">{children[2]}</div>
            </div>
        )
    }
}

props属性实现插槽

export class App extends Component {
    render(){
        return(
            <div>
                {/*给导航栏组件传递props*/}
                <NavBarTwo
                    leftslot={<button>按钮2</button>}
                    centerslot={<h2>呵呵呵</h2>}
                    rightslot={<i>斜体2</i>}
                />
            </div>
        )
    }
}
//通用导航栏组件
export class NavBar extends Component {
    render(){
        const {leftslot, centerslot, rightslot} = this.props
        return(
            <div className='nav-bar'>
                <div className="left">{leftslot}</div>
                <div className="center">{centerSlot}</div>
                <div className="right">{rightslot}</div>      
            </div>
        )
    }
}

ref获取组件和组件中的DOM

组件中如何获取DOM

  1. 在React的开发模式中,通常情况下不需要、也不建议直接操作DOM原生,但是某些特殊的情况,确实需要获取到DOM进行某些操作,官方不建议用js原生方法获取DOM。
  2. 可以通过设置refs来获取DOM,有三种方式:

     import React,{ Purecomponent, createRef}from'react'
     export class App extends Purecomponent{
         constructor() {
             super()
             this.titleRef= createRef()
             this.titleel = nul1
         }
         getNativeDoM(){
             //1.方式一:在React元素上绑定一个ref字符串
             //console.log(this.refs.coderzhong)
             //2.方式二:提前创建好ref对象,createRef(),将创建出来的对象绑定到元素,最常用该方法
             //console.log(this.titleRef.current)
             //3.方式三:传入一个回调函数,在对应的元素被渲染之后,回调函数被执行,并且将元素传入
             console.log(this.titleEl)
         }
         render(){
             return(
                 <div>
                     <h2 ref="coderzhong">Hello World</h2>
                     <h2 ref={this.titleRef}>你好啊,xxx</h2>
                     <h2 ref={el =>{ this.titleEl = el }}>你好啊</h2>
                     <button onclick={e => this.getNativeDoM()]>获取DOM</button>
                 </div>
         }
     }
     export default App
    

ref获取组件

  1. 获取类组件实例

     export class App extends Purecomponent{
         constructor(){
             super()
             this.hwRef = createRef()
         }
         getcomponent(){
             //获取Helloworld组件
             console.log(this.hwRef.current)
             //调用Helloworld组件内部的实例方法
             this.hwRef.current.test();
         }
         render(){
             return(
                 <div>
                     <Helloworld ref={this.hwRef}/>
                     <button onclick={e =>this.getcomponent()}>获取组件实例</button>
                 </div>
             )
         }
     }
    
  2. 无法获取函数式组件,但是可以获取函数式组件中的dom(略)

受控组件、非受控组件

  1. 在 HTML 中,表单元素(如<input>、<textarea>和<select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。即:比如input用键盘输入内容,浏览器会自动显示到输入框内。—-非受控组件
  2. 而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新.即react刷新页面必须通过setState。—–受控组件
  3. 这个“受控”指的是:被React控制,用setState来渲染界面。
  4. 那么如何将“非受控组件”转变为受控组件呢?
    1. 给非受控组件绑定value值。
    2. 监听非受控组件的输入操作onchange。
    3. 使用setState来渲染非受控组价
  5. 注意: 以上3个条件缺一不可,如果只有1,那么input则不可以输入文本。
  6. 举例:

     constructor(){
         super()
         //给input赋默认值
         this.state = {username : "123"}
     }
     inputchange(event){
         console.log("inputchange:",event.target.value)
         //3. 使用setState来渲染非受控组价
         this.setstate({ username:event.target.value })
     }
     render(){
         const { username}=this.state
         return(
             <div>
                 {/*受控组件:1. 给非受控组件绑定value值。2. 监听非受控组件的输入操作onchange*/}
                 <input type="text" value={username} onchange={le=>this.inputchange(e)}/>
             </div>
         )
     }
    

高阶组件

  1. 高阶函数:接收一个或者多个函数做为输入,或者输出一个函数。
  2. 高阶组件:Higher-Order Components,简称为 HOC,高阶组件参数为组件返回值为新组件的函数;
    1. 高阶组件 本身不是一个组件,而是一个函数;
    2. 这个函数的参数是一个组件,返回值也是一个组件,

使用场景

  1. 可以对组件进行过滤封装
  2. 模拟登录界面:登录就展示购物车,未登录就展示请登录,把这个是否登录的判断放到组件中

     //App.jsx
     import React, { PureComponent } from 'react'
     import Cart from './pages/Cart'
        
     export class App extends PureComponent {
       constructor() {
         super()
       }
        
       loginClick() {
         //模拟登录
         localStorage.setItem("token", "coderzh")
         // 强制更新
         this.forceUpdate()
       }
        
       render() {
         return (
           <div>
             App
             <button onClick={e => this.loginClick()}>登录</button>
             <Cart/>
           </div>
         )
       }
     }
     export default App
        
     //高阶组件,用于判断
     function loginAuth(OriginComponent) {
       return props => {
         // 从localStorage中获取token
         const token = localStorage.getItem("token")
        
         if (token) {
           return <OriginComponent {...props}/>
         } else {
           return <h2>请先登录, 再进行跳转到对应的页面中</h2>
         }
       }
     }
        
     export default loginAuth
        
     //购物车组件
     import React, { PureComponent } from 'react'
     import loginAuth from '../hoc/login_auth'
        
     export class Cart extends PureComponent {
       render() {
         return (
           <h2>Cart Page</h2>
         )
       }
     }
     //调用高阶组件函数
     export default loginAuth(Cart)
    

fragment

  1. 在之前的开发中,总是在一个组件中返回内容时包裹一个div元素:
  2. 也可以不渲染这样一个div:使用Fragment,Fragment 允许将子列表分组,而无需向 DOM 添加额外节点;
  3. React还提供了Fragment的短语法:它看起来像空标签 <> </>

     export class App extends Purecomponent {
         render(){
             return (
                 {/**Fragment不会被渲染/}
                 <Fragment>
                     <h2>我是App的标题</h2>
                 </Fragment>
             )
         }
     }
     export default App
    

组件的CSS样式导入

  1. 实现场景:实现一个评论列表.,创建CommentList.jsx

     import React from 'react'
     // 导入当前组件需要的子组件
     import CommentItem from './CommentItem.jsx'
     // 评论列表组件
     export default class CommentList extends React.Component {
       constructor(props) {
         super(props)
        
         // 定义当前评论列表组件的 私有数据
         this.state = {
           cmts: [
             { user: '张三', content: '哈哈,沙发' },
             { user: '张三2', content: '哈哈,板凳' },
             { user: '张三3', content: '哈哈,凉席' },
             { user: '张三4', content: '哈哈,砖头' },
             { user: '张三5', content: '哈哈,楼下山炮' }
           ]
         }
       }
        
       // 在 有状态组件中,render 函数是必须的,表示,渲染哪些 虚拟DOM元素并展示出来
       render() {
         //返回每一行item
         return <div>
           {/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */}
           {this.state.cmts.map((item, i)  => {
             return <div key={i}>
                 <h1>评论人:{item.user}</h1>
                 <h1>评论内容:{item.content}</h1>
             </div>
           })}
         </div>
       }
     }
    
  2. main.js

     // JS打包入口文件
     // 1. 导入 React包
     import React from 'react'
     import ReactDOM from 'react-dom'
     // 导入评论列表组件
     import CommentList from './components/comment1/CommentList.jsx'
     ReactDOM.render(<div>
       <CommentList></CommentList>
     </div>, document.getElementById('app'))
    
  3. 封装评论项:CommentItem
    1. 把列表的每一行封装成一个item
    2. 新建CommentItem.jsx
     import React from 'react'
     // 封装一个 评论项 组件,此组件由于不需要自己的 私有数据,所以直接定义为 无状态组件
     export default function CommentItem(props) {
         return <div>
             <h1>评论人:{props.user}</h1>
             <h1>评论内容:{props.content}</h1>
         </div>
     }
    
  4. CommentList.jsx中返回每一行item修改

        
     return <div>
       <h1 className="title">评论列表案例</h1>
       {/* 我们可以直接在 JSX 语法内部,使用 数组的 map 函数,来遍历数组的每一项,并使用 map 返回操作后的最新的数组 */}
       {this.state.cmts.map((item, i) => {
         // return <CommentItem user={item.user} content={item.content} key={i}></CommentItem>
         return <CommentItem {...item} key={i}></CommentItem>
       })}
     </div>
    

style内联样式:给CommentItem添加样式

  1. 如何添加样式?CommentItem.jsx文件

     // 注意: 如果要使用 style 属性,为 JSX 语法创建的DOM元素,设置样式,不能像网页中那么写样式;而是要使用JS语法来写样式
     // 在 写 style 样式的时候,外层的 { } 表示 要写JS代码了,内层的 { } 表示 用一个JS对象表示样式
     // 注意: 在 style 的样式规则中,如果 属性值的单位是 px, 则 px 可以省略,直接写一个 数值 即可
     export default function CommentItem(props) {
         return <div style=>
             <h1 style=>评论人:{props.user}</h1>
             <h1 style=>评论内容:{props.content}</h1>
         </div>
     }
    
  2. 优化

     export default function CommentItem(props) {
         //单独封装成对象
         const boxStyle = { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 }
        const titleStyle = { fontSize: 16, color: "purple" }
        const bodyStyle = { fontSize: 14, color: "red" } 
        //直接使用对象
         return <div style={boxStyle}>
             <h1 style={titleStyle}>评论人:{props.user}</h1>
             <h1 style={bodyStyle}>评论内容:{props.content}</h1>
         </div>
     }
    
  3. 优化2

     //把 样式对象,封装到唯一的一个对象中
     const inlineStyles = {
         boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
         titleStyle: { fontSize: 16, color: "purple" },
         bodyStyle: { fontSize: 14, color: "red" }
       }
     return <div style={inlineStyles.boxStyle}>
         <h1 style={inlineStyles.titleStyle}>评论人:{props.user}</h1>
         <h3 style={inlineStyles.bodyStyle}>评论内容:{props.content}</h3>
     </div>
    
  4. 优化3:

    1. 将样式独立封装到cmtItemStyle.js文件中去

       // 导入一个 样式的对象
       export default {
         boxStyle: { border: '1px solid #ccc', margin: '10px 0', paddingLeft: 15 },
         titleStyle: { fontSize: 16, color: "purple" },
         bodyStyle: { fontSize: 14, color: "red" }
       }
      
    2. CommentItem.js文件导入cmtItemStyle.js

       import inlineStyles from './cmtItemStyles.js'
      

CSS外联样式导入使用

优化4:CommentItem.js中不采用行内样式style方式

  1. 新建CommentItem.css

     /* 注意:当启用 CSS 模块化之后,这里所有的类名,都是私有的,如果想要把类名设置成全局的一个类,可以把这个类名,用 :global() 给包裹起来 */
     /* 当使用 :global() 设置了全局的 类样式之后,这个类不会被重命名 */
     //私有
     .box{
       border: 1px solid #ccc;
       padding-left: 15px;
       box-shadow: 0 0 6px #ccc;
       margin: 10px 0;
     }
     //公有,通过普通方式导入当前CSS文件的都可以使用,比如CommentList.jsx通过import CommentItem from './CommentItem.jsx',导入,只能使用:global配置的css属性
     /* 只有私有的类才会被重命名 */
     :global(.title){
       color:red;
       text-align: center;
     }
     //私有:只有通过模块化导入当前CSS文件的才能被使用
     .title{
       color: green;
       font-size: 16px;
     }
     //私有
     .body{
       font-size: 14px;
       color:red;
     }
    
  2. 模块化CSS:导入CSS时能够限制导入的CSS只能用于当前文件,避免css串用

    1. 在CommentItem.jsx导入CommentItem.css
     // 导入评论项的样式文件【这种直接 import '../路径标识符' 的 CSS 导入形式,并不是模块化的CSS】
     // import '../../css/commentItem.css'
     // 默认情况下,如果没有为 CSS 启用模块化,则接收到的 itemStyles 是个空对象,因为 .css 样式表中,不能直接通过 JS 的 export defualt 导出对象
     // 当启用 CSS 模块化之后(在webpack.config.js中配置启用CSS模块化,modules:true),导入 样式表得到的 itemStyles 就变成了一个 样式对象,其中,属性名是 在样式表中定义的类名,属性值,是自动生成的一个复杂的类名(防止类名冲突)
     import itemStyles from '../../css/commentItem.css'
     console.log(itemStyles)
        
     ...
    
     return <div className={itemStyles.box}>
         <h1 className={itemStyles.title}>评论人:{props.user}</h1>
         <h3 className={itemStyles.body}>评论内容:{props.content}</h3>
     </div>
     ...
    

css modules

  1. css modules并不是React特有的解决方案,而是所有使用了类似于webpack配置的环境下都可以使用的。
    1. 如果在其他项目中使用它,那么我们需要自己来进行配置,比如配置webpack.config.js中的modules: true等。
  2. React的脚手架已经内置了css modules的配置
  3. 使用方式
    1. 将XXX.css/.less/.scss 等样式文件都需要修改成 XXX.module.css/.module.less/.module.scss
    2. 引入: import itemStyles from '../../css/commentItem.module.css'
  4. 举例使用:

     //APP.module.css文件
     .title {
       font-size: 32px;
       color: green;
     }
        
     .content {
       font-size: 22px;
       color: orange;
     }
        
     .hy-title {
          
     }
        
     //App.jsx
     import React, { PureComponent } from 'react'
     import appStyle from "./App.module.css"
    
     export class App extends PureComponent {
       render() {
         return (
           <div>
             <h2 className={appStyle.title}>我是标题</h2>
             <p className={appStyle.content}>我是内容, 哈哈哈哈</p>
           </div>
         )
       }
     }
        
     export default App
    

绑定this并传参的三种方式

  1. 如果render中的标签点击事件绑定的是普通函数,那么普通函数中的this指向是undefind.
  2. 解决方案:
    1. 使用bind
    2. 使用箭头函数:把当前函数变成箭头函数
    3. 使用call/apply

方式一:bind绑定传递

  1. bind 的作用:修改函数内部的this指向,让函数内部的this,指向bind 参数列表中的 第一个参数
  2. bind 和 call/apply 之间的区别:call/apply 修改完this指向后,会立即调用前面的函数,但是 bind 只是修改this指向,并不会调用。
  3. 注意: bind 中的第一个参数,是用来修改 this 指向的,第一个参数后面的所有参数,都会当作将来调用 前面函数 时候的参数传递进去
  4. 创建BindThis.jsx组件

     import React from 'react'
     export default class BindThis extends React.Component {
       constructor(props) {
         super(props)
         this.state = {
           msg: '这是默认的msg'
         }
       }
       //render中的标签点击事件绑定的是普通函数
       render() {
         return <div>
           <h1>绑定This并传参的几种方式</h1>
           {/* 方式1:在 事件处理函数中,直接使用 bind 绑定 this 并传参 */}
           <input type="button" value="绑定this并传参的方式1" onClick={this.changeMsg1.bind(this, '🐷', '🍕')} />
           <hr />
           <h3>{this.state.msg}</h3>
         </div>
       }
        
       /**
       * 普通实例函数
       * 注意:这里的方式,是一个普通方法,因此,在触发的时候,这里的 this 是 undefined
        */
       changeMsg1(arg1, arg2,event) {
         //event,默认传递
         // console.log(this);
         this.setState({
           msg: '绑定this并传参的方式1' + arg1 + arg2
         })
       }
     }
    
  5. main.js导入

     import BindThis from './components/BindThis.jsx'
     //render中渲染
     ReactDOM.render(<div>
     <BindThis></BindThis>
     </div>, document.getElementById('app'))
    

方式二:在构造函数中bind绑定并传参

  1. 当一个函数,调用 bind 改变了this指向后,bind 函数调用的结果返回值,就是被改变this指向后的函数的引用;
  2. 实现普通函数changeMsg2

     changeMsg2(arg1, arg2) {
         // console.log(this);
         this.setState({
           msg: '绑定this并传参的方式2' + arg1 + arg2
         })
       }
    
  3. render中点击第二个按钮

     <input type="button" value="绑定this并传参的方式2" onClick={this.changeMsg2} />
    
  4. constructor绑定this并传参

     constructor(props) {
         super(props)
         this.state = {
           msg: '这是默认的msg'
         }
            
         // 绑定 this 并传参的方式2: 在构造函数中绑定并传参
         // 注意,当一个函数,调用 bind 改变了this指向后,bind 函数调用的结果返回值,就是被改变this指向后的函数的引用;
         // 注意: bind 不会修改 原函数的 this 指向
         this.changeMsg2 = this.changeMsg2.bind(this, '🚗', '👫')
     }
    

方式三:使用箭头函数绑定this并传参

  1. 实现箭头函数changeMsg3

     changeMsg3 = (event,arg1, arg2) => {
         // console.log(this);
         this.setState({
           msg: '绑定this并传参的方式3' + arg1 + arg2
         })
     }
    
  2. render中点击第三个按钮

     <input type="button" value="绑定this并传参的方式3" onClick={(event) => { this.changeMsg3(event,'😊', '😘') }} />
    

评论列表案例

pic

  1. 创建评论列表组件CmtList.jsx

     import React from 'react'
     import CMTItem from './CmtItem.jsx'
     import CMTBox from './CmtBox.jsx'
        
     // 评论列表组件
     export default class CMTList extends React.Component {
       constructor(props) {
         super(props)
         this.state = {
           list: [
             { user: 'zs', content: '123' },
             { user: 'ls', content: 'qqq' },
             { user: 'xiaohong', content: 'www' }
           ]
         }
       }
        
       // 在组件渲染完毕获取数据
       componentDidMount() {
         this.loadCmts()
       }
        
       render() {
         return <div>
           <h1>这是评论列表组件</h1>
        
           {/* 发表评论的组件 */}
           {/* react 中,只要是传递给 子组件的数据,不管是 普通的类型,还是方法,都可以使用 this.props 来调用 */}
           <CMTBox reload={this.loadCmts}></CMTBox>
        
           <hr />
        
        
           {/* 循环渲染一些评论内容组件 */}
           {this.state.list.map((item, i) => {
             return <CMTItem key={i} {...item}></CMTItem>
           })}
         </div>
       }
        
       // 从本地存储中加载 评论列表
       loadCmts = () => {
         var list = JSON.parse(localStorage.getItem('cmts') || '[]')
         this.setState({
           list
         })
       }
     }
    
  2. 创建评论item组件CmtItem.jsx

     import React from 'react'
     // 评论列表项组件
     export default class CMTItem extends React.Component {
        
       render() {
         return <div style=>
           <h3>评论人:{this.props.user}</h3>
           <h5>评论内容:{this.props.content}</h5>
         </div>
       }
     }
    
  3. 创建评论组件CmtBox.jsx

     import React from 'react'
    
     // 评论列表框组件
     export default class CMTBox extends React.Component {
        
       render() {
         return <div>
           <label>评论人:</label><br />
           <input type="text" ref="user" /><br />
           <label>评论内容:</label><br />
           <textarea cols="30" rows="4" ref="content"></textarea><br />
        
           <input type="button" value="发表评论" onClick={this.postComment} />
         </div>
       }
        
       postComment = () => {
         // 1. 获取到评论人和评论内容
         // 2. 从 本地存储中,先获取之前的评论数组
         // 3. 把 最新的这条评论,unshift 进去
         // 4. 在把最新的评论数组,保存到 本地存储中
         var cmtInfo = { user: this.refs.user.value, content: this.refs.content.value }
         var list = JSON.parse(localStorage.getItem('cmts') || '[]')
         list.unshift(cmtInfo)
         localStorage.setItem('cmts', JSON.stringify(list))
        
         this.refs.user.value = this.refs.content.value = ''
        
         this.props.reload()
       }
     }
    
  4. main.js中引用

     import React from 'react'
     import ReactDOM from 'react-dom'
     import CmtList from './components/Comment/CmtList.jsx'
     // 使用 render 函数渲染 虚拟DOM
     ReactDOM.render(<div>
       <BindThis></BindThis>
     </div>, document.getElementById('app'))
    

context特性

  1. 如果一个组件1嵌套组件2嵌套组件3,组件1为父组件,2为子组件,3为孙组件。
  2. 如果组件1要给组件3传递数据,通常的方法是通过props属性层层传递

     export default class Com1 extends React.Component {
       constructor(props) {
             super(props)
             this.state = {
               color: 'red'
             }
       }
       render() {
         return <div>
           <h1>这是 父组件 </h1>
           <Com2 color={this.state.color}></Com2>
         </div>
       }
     }
     // 中间的子组件
     class Com2 extends React.Component {
       render() {
         return <div>
           <h3>这是 子组件 </h3>
           <Com3 color={this.props.color}></Com3>
         </div>
       }
     }
        
     // 内部的孙子组件
     class Com3 extends React.Component {
       render() {
         return <div>
           <h5 style=>这是 孙子组件 </h5>
         </div>
       }
     } 
    
  3. 通过context直接传递

     // 最外层的父组件
     export default class Com1 extends React.Component {
       constructor(props) {
         super(props)
         this.state = {
           color: 'red'
         }
       }
        
       //   getChildContextTypes
       // 1. 在 父组件中,定义一个 function,这个function 有个固定的名称,叫做 getChildContext ,内部,必须 返回一个 对象,这个对象,就是要共享给 所有子孙自建的  数据
       getChildContext() {
         return {
           color: this.state.color
         }
       }
        
       // 2. 使用 属性校验,规定一下传递给子组件的 数据类型, 需要定义 一个 静态的(static) childContextTypes(固定名称,不要改)
       static childContextTypes = {
         color: ReactTypes.string // 规定了 传递给子组件的 数据类型
       }
       render() {
         return <div>
           <h1>这是 父组件 </h1>
           <Com2></Com2>
         </div>
       }
     }
     // 中间的子组件
     class Com2 extends React.Component {
       render() {
         return <div>
           <h3>这是 子组件 </h3>
           <Com3></Com3>
         </div>
       }
     }
    
     // 内部的孙子组件
     class Com3 extends React.Component {
        
       // 3. 上来之后,先来个属性校验,去校验一下父组件传递过来的 参数类型
       static contextTypes = {
         color: ReactTypes.string // 这里,如果子组件,想要使用 父组件通过 context 共享的数据,那么在使用之前,一定要先 做一下数据类型校验
       }
        
       render() {
         return <div>
           <h5 style=>这是 孙子组件  ---  {this.context.color} </h5>
         </div>
       }
     }
    

React过渡动画实现

  1. 在开发中,我们想要给一个组件的显示和消失添加某种过渡动画,可以很好的增加用户体验。
  2. 当然,可以通过原生的CSS来实现这些过渡动画,但是React社区为我们提供了react-transition-group用来完成过渡动画。
  3. 这个库可以帮助我们方便的实现组件的 入场 和 离场 动画,使用时需要进行额外的安装:

     //npm安装
     npm install react-transition-group --save
     //yarn安装
     yarn add react-transition-group
    

react-transition-group主要组件

  1. Transition:该组件是一个和平台无关的组件(不一定要结合CSS)
  2. CSSTransition: 在前端开发中,通常使用CSSTransition来完成过渡动画效果
  3. SwitchTransition: 两个组件显示和隐藏切换时,使用该组件
  4. TransitionGroup: 将多个动画组件包裹在其中,一般用于列表中元素的动画

CSSTransitio

  1. CSSTransition是基于Transition组件构建
  2. CSSTransition执行过程中,有三个状态:appear、enter、exit;
  3. 它们有三种状态,需要定义对应的CSS样式
    1. 第一类,开始状态:对于的类是-appear、-enter、exit;
    2. 第二类:执行动画:对应的类是-appear-active、-enter-active、-exit-active;
    3. 第三类:执行结束:对应的类是-appear-done、-enter-done、-exit-done;
  4. CSSTransition常见对应的属性:
    1. in:触发进入或者退出状态
      1. 如果添加了unmountOnExit={true},那么该组件会在执行退出动画结束后被移除掉;
      2. 当in为true时,触发进入状态,会添加-enter、-enter-acitve的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
      3. 当in为false时,触发退出状态,会添加-exit、-exit-active的class开始执行动画,当动画执行结束后,会移除两个class,并且添加-enter-done的class;
    2. classNames:动画class的名称
      1. 决定了在编写css时,对应的class名称:比如card-enter、card-enter-active、card-enter-done;
    3. timeout:过渡动画的时间
    4. appear:是否在初次进入添加动画(需要和in同时为true)
    5. unmountOnExit:退出后卸载组件
  5. CSSTransition对应的钩子函数:主要为了检测动画的执行过程,来完成一些JavaScript的操作
    1. onEnter:在进入动画之前被触发;
    2. onEntering:在应用进入动画时被触发;
    3. onEntered:在应用进入动画结束后被触发
import React, { createRef, PureComponent } from 'react'
import { CSSTransition } from "react-transition-group"
import "./style.css"

export class App extends PureComponent {
  constructor(props) {
    super(props)

    this.state = {
      isShow: true
    }

    this.sectionRef = createRef()
  }

  render() {
    const { isShow } = this.state

    return (
      <div>
        <button onClick={e => this.setState({isShow: !isShow})}>切换</button>
        {/* 实现过度动画*/}

        <CSSTransition 
          nodeRef={this.sectionRef}
          in={isShow} 
          unmountOnExit={true} 
          classNames="why" 
          timeout={2000}
          appear
          onEnter={e => console.log("开始进入动画")}
          onEntering={e => console.log("执行进入动画")}
          onEntered={e => console.log("执行进入结束")}
          onExit={e => console.log("开始离开动画")}
          onExiting={e => console.log("执行离开动画")}
          onExited={e => console.log("执行离开结束")}
        >
          <div className='section' ref={this.sectionRef}>
            <h2>哈哈哈</h2>
            <p>我是内容, 哈哈哈</p>
          </div>
        </CSSTransition>
      </div>
    )
  }
}

export default App
Table of Contents