1. hello React

React是Facebook出品的构建用户界面的js库,英文文档 https://reactjs.org/ 官方中文文档 https://react.docschina.org/

React的简单使用:

  1. 准备一个容器 如:
  2. 引入react的核心库,引入react-dom,用于支持react操作DOM,引入babel用于将jsx转换程js

        <!-- 引入react核心库 -->
          <script type="text/javascript" src="../js/react.development.js"></script>
        <!-- 引入react-dom,用于支持react操作DOM -->
          <script type="text/javascript" src="../js/react-dom.development.js"></script>
        <!-- 引入babel,用于将jsx转为js -->
          <script type="text/javascript" src="../js/babel.min.js"></script>
  3. 创建虚拟DOM,渲染虚拟DOM到页面

    /* 此处一定要写babel,表示里面写的是jsx语法 */    
        <script type="text/babel" >
            //1.创建虚拟DOM
            const VDOM = <h1>Hello,React</h1> /* 此处一定不要写引号,因为不是字符串 */
            //2.渲染虚拟DOM到页面
            ReactDOM.render(VDOM,document.getElementById('test'))
        </script>
  4. 渲染出来的效果

    image-20210224124536942

2. 虚拟DOM的两种创建方式

第一种使用jsx创建:

<script type="text/babel" > /* 此处一定要写babel,表示写的内容需要babel编译 */
        //1.创建虚拟DOM
        const VDOM = (  /* 此处一定不要写引号,因为不是字符串 */
            <h1 id="title">
                <span>Hello,React</span>
            </h1>
        )
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM,document.getElementById('test'))
    </script>

第二种使用js创建:

/*因为我们使用js就不需要引入babel来进行编译了*/
<script type="text/javascript" > 
        //1.创建虚拟DOM
        const VDOM = React.createElement('h1',{id:'title'},React.createElement('span',{},'Hello,React'))
        //2.渲染虚拟DOM到页面
        ReactDOM.render(VDOM,document.getElementById('test'))
</script>

当然可以看出来用js来写虚拟dom,当遇到嵌套的标签就会一直嵌套的写,所以用jsx方便些 ,其实js的写法就是jsx经过babel编译出来的结构写法。

关于虚拟DOM与真实的DOM:

  1. 虚拟DOM本质上是object对象
  2. 虚拟DOM本质上比较“轻”,真实DOM‘重’,虚拟DOM是react内部使用,没有真实DOM那么多的属性
  3. 虚拟DOM最终会被react转变为真实的DOM,呈现在页面上

3. jsx的语法规则

jsx是js的一种语法扩展,规则如下:

  1. 定义虚拟DOM时不需要写引号
  2. 标签中混入JS表达式时要用{}
  3. 样式的类名指定不要用class,要用className
  4. 内联样式,要用style={{key:value}}的形式去写
  5. 只能有一个根标签
  6. 标签必须闭合
  7. 标签首字母

    ​ (1).若小写字母开头,则将该标签转为html中同名元素,若html中无该标签对应的同名元素,则报错

    ​ (2).若大写字母开头,react就去渲染对应的组件,若组件没有定义,则报错

4. react中定义组件

函数式组件

<script type="text/babel">
        //1.创建函数式组件
        function MyComponent(){
            console.log(this); //此处的this是undefined,因为babel编译后开启了严格模式
            return <h2>我是用函数定义的组件(适用于【简单组件】的定义)</h2>
        }
        //2.渲染组件到页面
        ReactDOM.render(<MyComponent/>,document.getElementById('test'))
        /* 
            执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
                    1.React解析组件标签,找到了MyComponent组件。
                    2.发现组件是使用函数定义的,随后调用该函数,将返回的虚拟DOM转为真实DOM,随后呈现在页面中。
        */
</script>

类式组件

 <script type="text/babel">
        //1.创建类式组件
        class MyComponent extends React.Component {
            render(){
                //render是放在哪里的?—— MyComponent的原型对象上,供实例使用。
                //render中的this是谁?—— MyComponent的实例对象 <=> MyComponent组件实例对象。
                console.log('render中的this:',this);
                return <h2>我是用类定义的组件(适用于【复杂组件】的定义)</h2>
            }
        }
        //2.渲染组件到页面
        ReactDOM.render(<MyComponent/>,document.getElementById('test'))
        /* 
            执行了ReactDOM.render(<MyComponent/>.......之后,发生了什么?
                    1.React解析组件标签,找到了MyComponent组件。
                    2.发现组件是使用类定义的,随后new出来该类的实例,并通过该实例调用到原型上的render方法。
                    3.将render返回的虚拟DOM转为真实DOM,随后呈现在页面中。
        */
    </script>

5. 组件实例三大属性

state

  <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js

    //1.创建类组件,必须要继承react内置的类
   class MyComponent extends React.Component {
     //构造器调用几次---1次
     constructor(props) {
       console.log('constructor');
       super(props);
       //初始化状态
       this.state = {isHight:false}
       //解决demo中this指向问题
       this.demo = this.change.bind(this);
     }
     //render调用几次---1+n次,1是初始化那次,n是状态更新的次数
     render() {//render是放在类的原型对象上,供实例使用
              //render中的this指向---MyComponent的实例对象
              console.log('render');
       const {isHight} = this.state;
       return <h2 onClick={this.demo}>你{isHight ? '身体一定很好' : '是个LSP'}</h2>
     }
     change() {//change调用几次---点几次调用几次
       //注意demo作为onclick的回调,所以不是通过实例调用的,是直接调用
       //类中的方法默认开启了严格模式,所以demo中的this就指向undefined了
       //this.state.isHight = !this.state.isHight;
       //state中的状态不可直接更改,要借助内置的一个api进行更改
       console.log(this);
       const {isHight} = this.state;
       this.setState({//是一种合并方式的更新
        isHight:!isHight
       });
     }
   }
    //2.渲染组件到界面
    ReactDOM.render(<MyComponent/>,document.getElementById('test'));
    /* function demo() {
      console.log('点击了');
    } */
  </script>

上述方式要解决this的指向问题写法有些繁琐,以下为state的简写方式

    <script type="text/babel">
        //1.创建组件
        class Weather extends React.Component{
            //初始化状态,直接在类中写这种赋值语句表示给Weather的实例对象添加一个属性,名为state,值为一个对象
            state = {isHot:false,wind:'微风'}

            render(){
                const {isHot,wind} = this.state
                return <h1 onClick={this.changeWeather}>今天天气很{isHot ? '炎热' : '凉爽'},{wind}</h1>
            }

            //自定义方法————要用赋值语句的形式+箭头函数,箭头函数的this为上级作用域中的this刚好解决了函数中的this问题
            changeWeather = ()=>{
                const isHot = this.state.isHot
                this.setState({isHot:!isHot})
            }
        }
        //2.渲染组件到页面
        ReactDOM.render(<Weather/>,document.getElementById('test'))
                
    </script>

props

  1. 基本使用

      <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
        class Person extends React.Component {
          render() {
            const {name, age, sex} = this.props;
            return (
              <ul>
                <li>{name}</li>
                <li>{age}</li>
                <li>{sex}</li>
              </ul>
            )
          }
        }
        ReactDOM.render(<Person name="jeke" age="19" sex="男" />, document.getElementById('test'));
        ReactDOM.render(<Person name="kak" age="23" sex="女" />, document.getElementById('test1'));
        ReactDOM.render(<Person name="tom" age="14" sex="男" />, document.getElementById('test2'));
        //如果要传递很多属性这样写太过繁琐,可以使用对象解构写法
        //const p = {name:'老刘', age: 15, sex:'无'}
        //ReactDOM.render(<Person {...p} />, document.getElementById('test1'));
      </script>

    页面:
    image-20210224134500059

  1. 对props传入的参数进行限制

    //对props的限制操作需要引入一个新的库
    <script src="../js/prop-types.js"></script>
    <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
        class Person extends React.Component {
          constructor(props) {
            //构造器是否接收props,是否传递给super,取决于是否希望在构造器中通过this访问props
            super(props);
          }
          render() {
            const {name, age, sex, speak} = this.props;
            //this.props.name = "xxx";//这行代码会报错,因为props只读
            speak();
            return (
              <ul>
                <li>{name}</li>
                <li>{age}</li>
                <li>{sex}</li>
              </ul>
            )
          }
          static propTypes = {//对传入的属性进行限制
            name: PropTypes.string.isRequired,//表示必须传入一个字符串
            sex: PropTypes.string.isRequired,
            age: PropTypes.number,
            speak: PropTypes.func//这里要设置函数类型必须这样写
         }
          static defaultProps = {//对传入的数据默认值进行设置
            age: 17,
            sex: '你是啥子',
            speak() {
              console.log('我是默认的');
            }
         }
        }
        
        const p = {name:'老刘', age: 15, sex:'无'}
        function speak() {
          console.log('我说话了')
        }
        ReactDOM.render(<Person name="jeke" age={19} speak={speak} />, document.getElementById('test'));
        ReactDOM.render(<Person {...p} />, document.getElementById('test1'));
      </script>
  2. props在函数中的使用

        //对于函数式组件我们只能使用props这个属性了,而且对于props的限制也只能写在外面,因为没有static这个关键字
        function Student(props) {
          const {name, age, sex, speak} = props;
          return (
              <ul>
                <li>{name}</li>
                <li>{age}</li>
                <li>{sex}</li>
              </ul>
          )
        }
        Student.propTypes = {//对传入的属性进行限制
            name: PropTypes.string.isRequired,//表示必须传入一个字符串
            sex: PropTypes.string.isRequired,
            age: PropTypes.number,
            speak: PropTypes.func//这里要设置函数类型必须这样写
         }
         Student.defaultProps = {//对传入的数据默认值进行设置
            age: 17,
            sex: '你是啥子',
            speak() {
              console.log('我是默认的');
            }
         }
        const p = {name:'老刘', age: 15, sex:'无'}
        function speak() {
          console.log('我说话了')
        }
        ReactDOM.render(<Student name="jeke" age={19} />, document.getElementById('test1'));

ref

  1. 字符串形式的写法(已过时)

      <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
        class Person extends React.Component {
          showData = () => {
            console.log(this.refs.input1.value);
            //这种形式操作ref现在已经有点过时不推荐使用
          }
          showData2 = () => {
            console.log(this.refs.input2.value);
          }
          render(){
            return (
              <div>
                <input ref="input1" type="text" placeholder="点击按钮提示" />&nbsp;
                <button onClick={this.showData}>点击以下提示</button>&nbsp;
                <input ref="input2" onBlur={this.showData2} type="text" placeholder="失去焦点提示" />
              </div>
            )
          }
        }
        ReactDOM.render(<Person />, document.getElementById('test'));
      </script> 
  1. 回调形式(常用)

      <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
        class Person extends React.Component {
          showData = () => {
            console.log(this.input1.value);
          }
          showData2 = () => {
            console.log(this.input2.value);
          }
          render(){
            return (
              <div>
                <input ref={c => this.input1 = c} type="text" placeholder="点击按钮提示" />&nbsp;
                <button onClick={this.showData}>点击以下提示</button>&nbsp;
                <input onBlur={this.showData2} ref={c => this.input2 = c} type="text" placeholder="失去焦点按钮提示" />&nbsp;
              </div>
            )
          }
        }
        ReactDOM.render(<Person />, document.getElementById('test'));
      </script>

    回调形式中ref内联写法函数调用次数问题,ref的回调函数执行几次??? 第一次启动时执行一次之后更新render后就会执行两次,可以将内联写法改为外部调用形式来避免这种多次调用,当然本身这个问题没啥影响。

  2. createRef写法(常用)

      <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
        class Person extends React.Component {
          /* 
             React.createRef调用后可以返回一个容器,该容器可以存储被ref所标识的节点
             注意该方法是"专人专用"的,只能存一个,多个ref的话就只能不同命名来创建多个容器      
           */
          myRef = React.createRef(); 
          state = {
            isHot:true
          }
    
          showInfo = () => {
            console.log(this.myRef.current.value);
          }
          change = () => {
            const {isHot} = this.state;
            this.setState({
              isHot:!isHot
            }); 
          }
          saveInput = (c) => {
            this.input1 = c;
            console.log('@',c);
          }
          render() {
            const {isHot} = this.state;
            return (
              <div>
                <h1 onClick={this.change}>今天很{isHot ? '炎热' : '凉爽'}</h1>
                <input ref={this.myRef} type="text" />
                <button onClick={this.showInfo}>点击提示数据</button>
              </div>
            )
          }
        }
        ReactDOM.render(<Person />, document.getElementById('test'));
      </script>

6. 事件处理

    <script type="text/babel">
        //创建组件
        class Demo extends React.Component{
            /* 
                (1).通过onXxx属性指定事件处理函数(注意大小写)
                        a.React使用的是自定义(合成)事件, 而不是使用的原生DOM事件 —————— 为了更好的兼容性
                        b.React中的事件是通过事件委托方式处理的(委托给组件最外层的元素) ————————为了的高效
                (2).通过event.target得到发生事件的DOM元素对象 ——————————不要过度使用ref
             */
            //创建ref容器
            myRef = React.createRef()
            myRef2 = React.createRef()

            //展示左侧输入框的数据
            showData = (event)=>{
                console.log(event.target);
                alert(this.myRef.current.value);
            }

            //展示右侧输入框的数据
            showData2 = (event)=>{
                alert(event.target.value);
            }

            render(){
                return(
                    <div>
                        <input ref={this.myRef} type="text" placeholder="点击按钮提示数据"/>&nbsp;
                        <button onClick={this.showData}>点我提示左侧的数据</button>&nbsp;
                        <input onBlur={this.showData2} type="text" placeholder="失去焦点提示数据"/>&nbsp;
                    </div>
                )
            }
        }
        //渲染组件到页面
        ReactDOM.render(<Demo a="1" b="2"/>,document.getElementById('test'))
    </script>

7. 高阶函数柯里化

  <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js
    //#region   
      /*
       高阶函数:如果一个函数符合下面2个规范中的任何一个,那该函数就是高阶函数。
                                    1.若A函数,接收的参数是一个函数,那么A就可以称之为高阶函数。
                                    2.若A函数,调用的返回值依然是一个函数,那么A就可以称之为高阶函数。
                                    常见的高阶函数有:Promise、setTimeout、arr.map()等等

                    函数的柯里化:通过函数调用继续返回函数的方式,实现多次接收参数最后统一处理的函数编码形式。 
                        function sum(a){
                            return(b)=>{
                                return (c)=>{
                                    return a+b+c
                                }
                            }
                        }
      */
    //#endregion 
    class Login extends React.Component {
      //初始化状态
      state = {
        username: '张三',
        password: ''
      }
      handleSubmit = () => {
        event.preventDefault();//阻止默认事件
        const {username, password} = this.state;
        alert(`用户名:${username},密码:${password}`);
      }
      //相同的操作尽量统一一个函数来操作
      //保存用户名到状态中
      saveFormdata = (dataType) => {
        return (event) => {
          this.setState({[dataType]: event.target.value})
        }
        
      }
      render() {
        return (
                <form onSubmit={this.handleSubmit}>
                  用户名:<input onChange={this.saveFormdata('username')}  type="用户名" name="username" />
                  密码:<input onChange={this.saveFormdata('password')} type="password" name="password" />
                  <button>登录</button>
                </form>
        );
      }
    }
    ReactDOM.render(<Login/>, document.getElementById('test'));

  </script>

8. 组件的生命周期

  1. 旧版的生命周期
    2_react生命周期(旧)
    <script type="text/babel">
        /* 
                1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
                                    1.    constructor()
                                    2.    componentWillMount()
                                    3.    render()
                                    4.    componentDidMount() =====> 常用
                                                    一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
                2. 更新阶段: 由组件内部this.setSate()或父组件render触发
                                    1.    shouldComponentUpdate()
                                    2.    componentWillUpdate()
                                    3.    render() =====> 必须使用的一个
                                    4.    componentDidUpdate()
                3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
                                    1.    componentWillUnmount()  =====> 常用
                                                    一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
        */
        //创建组件
        class Count extends React.Component{

            //构造器
            constructor(props){
                console.log('Count---constructor');
                super(props)
                //初始化状态
                this.state = {count:0}
            }

            //加1按钮的回调
            add = ()=>{
                //获取原状态
                const {count} = this.state
                //更新状态
                this.setState({count:count+1})
            }

            //卸载组件按钮的回调
            death = ()=>{
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            //强制更新按钮的回调
            force = ()=>{
                this.forceUpdate()
            }

            //组件将要挂载的钩子
            componentWillMount(){
                console.log('Count---componentWillMount');
            }

            //组件挂载完毕的钩子
            componentDidMount(){
                console.log('Count---componentDidMount');
            }

            //组件将要卸载的钩子
            componentWillUnmount(){
                console.log('Count---componentWillUnmount');
            }

            //控制组件更新的“阀门”
            shouldComponentUpdate(){//返回true才会更新
                console.log('Count---shouldComponentUpdate');
                return true
            }

            //组件将要更新的钩子
            componentWillUpdate(){
                console.log('Count---componentWillUpdate');
            }

            //组件更新完毕的钩子
            componentDidUpdate(){
                console.log('Count---componentDidUpdate');
            }

            render(){
                console.log('Count---render');
                const {count} = this.state
                return(
                    <div>
                        <h2>当前求和为:{count}</h2>
                        <button onClick={this.add}>点我+1</button>
                        <button onClick={this.death}>卸载组件</button>
                        <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
                        <A />
                    </div>
                )
            }
        }
        
        //父组件A
        class A extends React.Component{
            //初始化状态
            state = {carName:'奔驰'}

            changeCar = ()=>{
                this.setState({carName:'奥拓'})
            }

            render(){
                return(
                    <div>
                        <div>我是A组件</div>
                        <button onClick={this.changeCar}>换车</button>
                        <B carName={this.state.carName}/>
                    </div>
                )
            }
        }
        
        //子组件B
        class B extends React.Component{
            //组件将要接收新的props的钩子
            componentWillReceiveProps(props){//初始化render时不执行
                console.log('B---componentWillReceiveProps',props);
            }

            //控制组件更新的“阀门”
            shouldComponentUpdate(){
                console.log('B---shouldComponentUpdate');
                return true
            }
            //组件将要更新的钩子
            componentWillUpdate(){
                console.log('B---componentWillUpdate');
            }

            //组件更新完毕的钩子
            componentDidUpdate(){
                console.log('B---componentDidUpdate');
            }

            render(){
                console.log('B---render');
                return(
                    <div>我是B组件,接收到的车是:{this.props.carName}</div>
                )
            }
        }
        
        //渲染组件
        ReactDOM.render(<Count/>,document.getElementById('test'))
    </script>

初始状态:
image-20210224144404299

父组件修改自己的state后(点击 点我+1)
image-20210224144528400

  1. 新版生命周期

3_react生命周期(新)

    <script type="text/babel">
        //创建组件
        class Count extends React.Component{
            /* 
                1. 初始化阶段: 由ReactDOM.render()触发---初次渲染
                                1.    constructor()
                                2.    getDerivedStateFromProps 
                                3.    render()
                                4.    componentDidMount() =====> 常用
                                            一般在这个钩子中做一些初始化的事,例如:开启定时器、发送网络请求、订阅消息
                2. 更新阶段: 由组件内部this.setSate()或父组件重新render触发
                                1.    getDerivedStateFromProps
                                2.    shouldComponentUpdate()
                                3.    render()
                                4.    getSnapshotBeforeUpdate
                                5.    componentDidUpdate()
                3. 卸载组件: 由ReactDOM.unmountComponentAtNode()触发
                                1.    componentWillUnmount()  =====> 常用
                                            一般在这个钩子中做一些收尾的事,例如:关闭定时器、取消订阅消息
            */
            //构造器
            constructor(props){
                console.log('Count---constructor');
                super(props)
                //初始化状态
                this.state = {count:0}
            }

            //加1按钮的回调
            add = ()=>{
                //获取原状态
                const {count} = this.state
                //更新状态
                this.setState({count:count+1})
            }

            //卸载组件按钮的回调
            death = ()=>{
                ReactDOM.unmountComponentAtNode(document.getElementById('test'))
            }

            //强制更新按钮的回调
            force = ()=>{
                this.forceUpdate()
            }
            
            //从props中得到派生的状态,若state的值都取决于props就可以使用这个周期函数了
            static getDerivedStateFromProps(props,state){
                console.log('getDerivedStateFromProps',props,state);
                return null
            }

            //在更新之前获取快照
            getSnapshotBeforeUpdate(){
                console.log('getSnapshotBeforeUpdate');
                return 'atguigu'
            }

            //组件挂载完毕的钩子
            componentDidMount(){
                console.log('Count---componentDidMount');
            }

            //组件将要卸载的钩子
            componentWillUnmount(){
                console.log('Count---componentWillUnmount');
            }

            //控制组件更新的“阀门”
            shouldComponentUpdate(){
                console.log('Count---shouldComponentUpdate');
                return true
            }

            //组件更新完毕的钩子
            componentDidUpdate(preProps,preState,snapshotValue){
                console.log('Count---componentDidUpdate',preProps,preState,snapshotValue);
            }
            
            render(){
                console.log('Count---render');
                const {count} = this.state
                return(
                    <div>
                        <h2>当前求和为:{count}</h2>
                        <button onClick={this.add}>点我+1</button>
                        <button onClick={this.death}>卸载组件</button>
                        <button onClick={this.force}>不更改任何状态中的数据,强制更新一下</button>
                    </div>
                )
            }
        }
        
        //渲染组件
        ReactDOM.render(<Count count={199}/>,document.getElementById('test'))
    </script>

getSnapshotBeforeUpdate的使用案例

做一个小效果,一个不断增加条数的新闻列表,要求滚动条滑倒那里的内容不会因为内容高度增加而被压下去

  <script type="text/babel">//此处要写type="text/babel",表明写的是jsx代码然后被babel转换为js

    class NewsList extends React.Component {
      state = {
        newsArr: []
      }
      //组件加载完后
      componentDidMount() {
        setInterval(() => {
          const {newsArr} = this.state;
          const news = '新闻' + (newsArr.length + 1);//获取新闻序号加1
          this.setState({newsArr: [news, ... newsArr]});//添加到状态中去
        }, 1000);
        
      }
      getSnapshotBeforeUpdate() {
        return this.list.scrollHeight;//获取改变前的滚动条高度
      }
      componentDidUpdate(preProps, preState, snapshotValue) {
        this.list.scrollTop += this.list.scrollHeight - snapshotValue;//修改滚动条距离顶部的高度来抵消文字被顶上去
      }
      render() {
        return (
          <div ref={c => this.list = c} className="list">
            {
              this.state.newsArr.map((item, index) => {
               return <div key={index} className="news" >{item}</div>
              })
            }
          </div>
        )
      }
    }
    ReactDOM.render(<NewsList />, document.getElementById('test'));
  </script>
Last modification:February 25, 2021
如果觉得我的文章对你有用,请随意赞赏