React学习(3)组件

定义组件的2种方式

工厂函数组件

DEMO:

<div id="test"></div>
<script type="text/babel">
    function MyComponent() {
        return <h2>工厂函数组件(无状态/简单组件)</h2>
    }
    ReactDOM.render(<MyComponent/>,document.getElementById('test'))
</script>

ES6类的方式定义组件

DEMO:

<div id="test"></div>
class MyComponent2 extends React.Component{
    //重写父类的render方法
    render(){
        return <h2>ES6类组件(有状态/复杂组件)</h2>
    }
}
ReactDOM.render(<MyComponent2/>,document.getElementById('test'))

注意

1) 组件名必须首字母大写
2) 虚拟DOM元素只能有一个根元素
3) 虚拟DOM元素必须有结束标签

组件三大属性

state、props、refs

state

1) state是组件对象最重要的属性, 值是对象(可以包含多个数据)
2) 组件被称为”状态机”, 通过更新组件的state来更新对应的页面显示(重新渲染组件)

DEMO(点击文字变化内容):
效果:

代码:

<div id="demo"></div>
<script type="text/babel">
    class Weather extends React.Component{
        constructor(props) {
            super(props);
            this.state = {
                isHot:true
            }
            this.clickHandle = this.clickHandle.bind(this)
        }
        clickHandle(){
            let {isHot} = this.state
            isHot = !isHot
            this.setState({
                isHot
            })
        }
        render(){
            return <h1 onClick={this.clickHandle}>今天天气很{this.state.isHot?'炎热':'凉爽'}</h1>
        }
    }
    ReactDOM.render(<Weather/>,document.getElementById('demo'))
</script>

以上代码的简写方式:

class Weather extends React.Component {
    state = {
        isHot: true
    }
    handleClick = () => {
        let {isHot} = this.state
        isHot = !isHot
        this.setState({isHot})
    }
    render() {
        return <h1 onClick={this.handleClick}>今天天气很{this.state.isHot ? '炎热' : '凉爽'}</h1>
    }
}
ReactDOM.render(<Weather/>, document.getElementById('demo'))
  • 强烈注意:
    1) 组件内置的方法中的this为组件对象
    2) 在组件类中自定义的方法中this为undefined
    a. 强制绑定this: 通过函数对象的bind()
    b. 箭头函数(ES6模块化编码时才能使用)
    3) 状态数据,不能直接修改或更新

props

1) 每个组件对象都会有props(properties的简写)属性
2) 组件标签的所有属性都保存在props中

  • 作用
    1) 通过标签属性从组件外向组件内传递变化的数据
    2) 注意: 组件内部不要修改props数据

效果:

代码:

<div id="demo1"></div>
<div id="demo2"></div>
<script type="text/babel">
    class Person extends React.Component {
        render() {
            let {name,sex,age} = this.props
            return (
                <div>
                    <ul>
                        <li>姓名:{name}</li>
                        <li>性别:{sex}</li>
                        <li>年龄:{age}</li>
                    </ul>
                    <hr/>
                </div>
            )
        }
    }

    let p1 = {
        name:'周杰伦',
        sex:'男',
        age:34,
    }
    let p2 = {
        name:'蔡依林',
        sex:'女',
        age:33,
    }
    ReactDOM.render(<Person name={p1.name} sex={p1.sex} age={p1.age}/>, document.getElementById('demo1'))
    ReactDOM.render(<Person {...p2}/>, document.getElementById('demo2'))
</script>

在以上代码基础上增加参数类型限制并设置默认值

<!--引入限制参数类型及必要性的库-->
<script src="./js/prop-types.js"></script>
class Person extends React.Component {
    //限制传参的类型
    static propTypes = {
        name: PropTypes.string.isRequired,
        sex: PropTypes.string.isRequired,
        age: PropTypes.number
    }
    //设置默认值
    static defaultProps = {
        age: 18
    }

    render() {
        let {name, sex, age} = this.props
        return (
            <div>
                <ul>
                    <li>姓名:{name}</li>
                    <li>性别:{sex}</li>
                    <li>年龄:{age}</li>
                </ul>
                <hr/>
            </div>
        )
    }
}
let p1 = {
    name: '周杰伦',
    sex: '男',
    age: '34',
}
let p2 = {
    name: '蔡依林',
    sex: '女',
}
ReactDOM.render(<Person name={p1.name} sex={p1.sex} age={p1.age}/>, document.getElementById('demo1'))
ReactDOM.render(<Person {...p2}/>, document.getElementById('demo2'))

  • 组件的props和state属性的区别
    1) state: 组件自身内部可变化的数据
    2) props: 从组件外部向组件内部传递数据, 组件内部只读不修改

refs

需求: 自定义组件, 功能说明如下:
1. 点击按钮, 提示第一个输入框中的值
2. 当第2个输入框失去焦点时, 提示这个输入框中的值

DEMO效果:

老语法(常用):

<div id="demo"></div>

<script type="text/babel">
    class UserInput extends React.Component {
        clickHandle = () => {
            console.log(this.refs)
            let {input1} = this.refs
            alert(input1.value)
        }
        blurHandle = (event) => {
            alert(event.target.value)
        }

        render() {
            return (
                <div>
                    <input type="text" ref="input1"/> 
                    <button type="button" onClick={this.clickHandle}>点击提示</button> 
                    <input type="text" onBlur={this.blurHandle}/>
                </div>
            )
        }
    }

    ReactDOM.render(<UserInput/>, document.getElementById('demo'))
</script>

新语法1(回调ref方式,不推荐使用):

class UserInput extends React.Component {
    clickHandle = () => {
        console.log(this)
        let {input1} = this
        alert(input1.value)
    }
    blurHandle = (event) => {
        alert(event.target.value)
    }

    render() {
        return (
            <div>
                <input type="text" ref={(input) => {this.input1 = input}}/> 
                <button type="button" onClick={this.clickHandle}>点击提示</button> 
                <input type="text" onBlur={this.blurHandle}/>
            </div>
        )
    }
}

ReactDOM.render(<UserInput/>, document.getElementById('demo'))

新语法2(createRef方式):

class UserInput extends React.Component {
    input1 = React.createRef()  //创建一个装ref的容器(对象),只能保存一个ref
    input2 = React.createRef()  //创建一个装ref的容器(对象),只能保存一个ref
    clickHandle = () => {
        console.log(this)
        alert(this.input1.current.value)
    }
    blurHandle = () => {
        alert(this.input2.current.value)
    }

    render() {
        return (
            <div>
                <input type="text" ref={this.input1}/> 
                <button type="button" onClick={this.clickHandle}>点击提示</button> 
                <input type="text" ref={this.input2} onBlur={this.blurHandle}/>
            </div>
        )
    }
}

ReactDOM.render(<UserInput/>, document.getElementById('demo'))

综合练习 todo list

实现效果:

代码:

<div id="demo"></div>

<script type="text/babel">
    class App extends React.Component {
        state = {
            todos: ['吃饭', '睡觉', '打豆豆'],
        }

        //状态在哪里,更新状态的方法就在哪里
        addTodos = (data) => {
            //1.获取原数据,复制原数组为一个新数组
            let todos = [...this.state.todos]
            //2.把数据追加进去
            todos.unshift(data)
            //3.更新状态
            this.setState({todos})
        }

        render() {
            let {todos} = this.state

            return (
                <div>
                    <h1>Simple Todo List</h1>
                    <Add count={todos.length} addTodos={this.addTodos}/>
                    <List todos={todos}/>
                </div>
            )
        }
    }

    class Add extends React.Component {

        addItem = () => {
            let {addTodos} = this.props
            //1.获取用户输入
            let value = this.item.value.trim()
            //2.将用户的输入追加到todos数组
            addTodos(value)
            //3.清空输入框
            this.item.value = ''
        }

        render() {
            let {count} = this.props

            return (
                <div>
                    <input type="text" ref={(input) => {
                        this.item = input
                    }}/>
                    <button onClick={this.addItem}>Add #{count}</button>
                </div>
            )
        }
    }

    class List extends React.Component {
        render() {
            let {todos} = this.props
            return (
                <ul>
                    {todos.map((item, index) => {
                        return <li key={index}>{item}</li>
                    })}
                </ul>
            )
        }
    }

    ReactDOM.render(<App/>, document.getElementById('demo'))
</script>

受控组件与非受控组件

受控组件

在HTML中,标签<input><textarea><select>的值的改变通常是根据用户输入进行更新。在React中,可变状态通常保存在组件的状态属性中,并且只能使用setState()更新,而呈现表单的React组件也控制着在后续用户输入时该表单中发生的情况,以这种由React控制的输入表单元素而改变其值的方式,称为:“受控组件”。

非受控组件

表单数据由DOM本身处理。即不受setState()的控制,与传统的HTML表单输入相似,input输入值即显示最新值(使用 ref从DOM获取表单值)

DEMO需求:
自定义包含表单的组件
1. 界面如图所示
2. 输入用户名密码后, 点击登陆提示输入信息
3. 密码是点击时候直接获取的,用户名是当用户输入时存入状态
4. 不提交表单

DEMO实现效果:

代码:

class Login extends React.Component {
    state = {
        username: ''
    }
    clickHandle = (event) => {
        event.preventDefault()
        let {username} = this.state
        let {password} = this
        alert(`用户名:${username},密码:${password.value}`)
    }
    changeHandle = (event) => {
        let username = event.target.value
        this.setState({username})
    }

    render() {
        return (
            <form action="" method="post">
                用户名:<input type="text" onChange={this.changeHandle}/>&nbsp;
                密码:<input type="password" ref={(input) => {this.password = input}}/>&nbsp;
                <button onClick={this.clickHandle}>提交</button>
            </form>
        )
    }
}

ReactDOM.render(<Login/>, document.getElementById('demo'))

生命周期

生命周期图解

  • DEMO需求:
  1. 让指定的文本做显示/隐藏的渐变动画
  2. 切换持续时间为2S
  3. 点击按钮从界面中移除组件界面
  • DEMO效果:
  • DEMO代码

<div id="demo"></div>
<script type="text/babel">
    class Life extends React.Component {

        state = {
            'opacity': 1
        }

        //组件被挂载时执行
        componentDidMount() {
            let {opacity} = this.state
            this.timeId = setInterval(() => {
                console.log('定时器执行')
                opacity -= 0.1
                if (opacity < 0) {
                    opacity = 1
                }
                this.setState({
                    opacity
                })
            }, 200)
        }

        //组件将被卸载时执行
        componentWillUnmount() {
            clearInterval(this.timeId)
        }

        removeComponent() {
            ReactDOM.unmountComponentAtNode(document.getElementById('demo'))
        }

        render() {
            let {opacity} = this.state
            return (
                <div>
                    <h2 style={{opacity}}>文本逐渐消失</h2>
                    <button onClick={this.removeComponent}>点我隐藏</button>
                </div>
            )
        }
    }

    ReactDOM.render(<Life/>, document.getElementById('demo'))
</script>

初始化

触发条件:ReactDOM.render(<MyComponent/>)
执行顺序:

constructor()
componentWillMount()  //将被挂载
render()   //提供虚拟DOM,可能会调用多次(1+n)。
componentDidMount()  //启动定时器、发送Ajax请求、只执行一次。

更新

触发条件:this.setState({})
执行顺序:

componentWillUpdate()
render()
componentDidUpdate()

卸载

触发条件:ReactDOM.unmountComponentAtNode()
执行顺序:

componentWillUnmount()  //收尾工作,例如:清除定时器,只执行一次。

虚拟DOM与DOM Diff算法

  • 基本原理图

发表评论

邮箱地址不会被公开。 必填项已用*标注