ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] ## pre-notify > previously:[http://a1](http://a) (づ ̄ 3 ̄)づ此文会根据文档的变动而不断更新 ## React中的element是什么 上回我们解释了什么是`JSX`,`JSX`是React中对Javascript语法的延伸,它允许我们使用`JSX tag`来构建一个虚拟DOM作为真实DOM的小型**描述文档**(它本身是一个Javascript对象)。 其中,一个`JSX tag`就是一个`React Element`。 ### 条件渲染 我们说过`JSX`是能参与js流程控制的,So我们能通过`if`一类的来决定一个`React Element`什么时候进行渲染什么时候不要。 ``` let element = ( <div> {isLogin}&& <h2>hello {userName}</h2> </div> ); ``` 也支持三元 ## element和component 上面我们已经知道一个`JSX tag`就是一个`React Element`,但一个`JSX tag`也可以是一个`React Component`。 它长得像这样 ``` let elements = <Component10086 /> ``` 这。。。和之前的有撒区别? 首先我们需要注意一个代表`Component`/组件的`JSX tag`**必须首字母大写**,否则它就会把它当做一个普通的element去渲染。 嗯。。。很自然我们会提出一个疑问,那当做普通的element去渲染和当做一个组件去渲染有什么区别呢? 其实,**React中的组件就像是Javascript 中的函数**,可以接收任意的输入(我们称之为props)并且能够返回React elements,这和我们直接得到`React Element`的区别在于,我们能在函数内部再对element进行一次封装,做一些条件渲染,设置state什么的。 --- So,我们光创建一个`JSX tag`是不够的,我们还需要定义一个对应的函数 ``` function Component10086(props){ return <h1>Hello Component!</h1>; } ``` 这个函数有一个`pros`属性,我们可以像调用函数一样在创建一个`JSX tag`时传递参数给这个函数,像这样 ``` let elements = <Component10086 prop1='第一个参数' prop2='第二个参数' /> // --- --- --- //也支持... 来传递多个属性 let data = { prop1:'第一个参数' ,prop2:'第二个参数' } let elements = <Component10086 {...data}/> ``` 上面的`prop1`和`prop2`两个键值对会**被封装成一个`props`对象**作为函数的参数传入 ``` function Component10086(props){ return ( <h1 className={props.prop2}/*当做属性渲染*/> Hello Component! {props.prop1} //当做children渲染 </h1>; ) } ``` >[important] **注意**:在JSX那一篇中我们已经说过JSX是javascript语法的延伸,这意味着一个`JSX tag`可以被赋值给一个js变量,能参与for和if流程控制,能作为函数传参,能够被当做函数返回值。**So组件传参时也能将一个JSX tag做为参数进行传递** --- ## 渲染React Element 我们已经知道如何构建一个虚拟DOM来描述真实的DOM ``` let element = <h1 className={class1}>hello React!</h1> ``` (以上经过babel编译后,我们可以发现它其实是调用`React.createELement`来创建一个javascript对象来描述真实dom,上回已详细说过不再赘述) >[danger] 引入(import)react库时,`React`必须是首字母大写,因为编译后我们调用createElement方法时就是首字母大写的React 但怎么将这个虚拟DOM转换成真正的DOM渲染在页面上呢? 这就是我们React中`react-dom`库所做的事情了。引入这个库后,我们能调用其中的`render`方法将对应的真实DOM渲染到指定的**挂载点**上。 ``` import ReactDOM from 'react-dom'; ... ReactDOM.render( element ,document.geElementById('root') ) ``` 值得注意的是如果这个`React Element`被重新渲染,**react只会重新渲染这个element中发生改变的dom节点**。(我们说过一个`JSX tag`可以有`children`)。 --- 同样的,我们能使用同样的方式对一个组件进行渲染, ``` ReactDOM.render( <Component10086 prop1='...' /> ,document.geElementById('root') ) ``` 那么,`render`是怎样区分渲染的是一个组件还是一个普通的element呢? 我们调用`ReactDOM.render`去渲染一个`JSX tag`时, 我们首先会查看这个tag的首字母是否是大写,如果是大写就代表是一个`component`,那么react就会把它当做一个组件去渲染, 它会首先将`JSX tag`做为参数传递的属性封装成一个props对象,然后再将这个对象传递给组件函数 组件函数接收到参数对象后会进行一系列处理,最终返回处理完成后的`React element`。 最终,`render`拿到element转换成真正的DOM元素渲染到页面上。 ## component与pure function 关于`React Component`,有一点我们需要注意的是,所有的react组件都必须是一个纯函数。 什么是纯函数呢? > Such functions are called “pure” because they do not attempt to change their inputs, and always return the same result for the same inputs. React官方给出的即是是,相同的输入会产生相同的输出,并且我们不能在函数内部更改传递过来的`props`。 这意味着,`props`是静止的,不可更改的,但一些交互性灰常强的UI组件的一些属性是经常会变化的。 那怎么解决这个问题呢?这就是后话了,后面讲到`React state`就是用来解决这个问题的。 >[important] **注意:** 如果属性是不参与`render()`的那么它就**不应该**被设计成`state` ## 函数式组件和类组件 组件有两种形式,函数式的和类形式的, ``` //function function Welcome(props) { return <h1>Hello, {props.name}</h1>; } //class class Welcome extends React.Component { render() { return <h1>Hello, {this.props.name}</h1>; } } ReactDOM.render(<Welcome name='ahhh' age='123'/>,document.getElementById('root')); ``` 此时这两种写法是等价的,但! class类的形式支持一些函数式组件不支持的功能,比如说`state`。 ## state/状态 首先`state`也是prop,但不同于普通的`props`。 普通的`props`我们可以在`JSX tag`上传参,react会自动帮我们将这些参数打包后挂载到组件上(`this.props`)。 而`state`需要我们在组件对象上手动设置, ### setState 首先我们在组件中**初始化**一个组件实例的`state` ``` class Clock extends React.Component{ constructor(props){ super(props); // 在构造函数内部定义初始状态 this.state = {date:new Date()}; } ``` 如果这个`state`需要发生改变,我们需要注意,我们不能直接通过`this.state.date = xxx` 这样的形式去改变state,这样是不会触发`render()`进行重绘的。 我们需要通过 `this.setState` 方法(来自继承的React.Component)来改变我们原有的`state`。 语法: `setState(updater[, callback])`,**注意此方法是支持回调的** ``` this.setState({ date:new Date() }) ``` 另外关于`setState`有两点需要额外注意 #### state设置更新时的自动合并机制 我们可能在一个构造函数中初始化多个`state`键值对 ``` ... this.state = { state1:'xx' state2:'yy' } .. ``` `React state`更新时的自动合并机制允许我们这样去更新state ``` this.setState({ state1:'aaa' }) ``` 可以发现我们只需要在setState中填上我们要更改的部分而不是整个state对象。 #### setState是异步的 我们可能在一个方法中连续使用多次setState,但由于设置是异步的,我们不能在第二次调用setState方法时拿到第一次调用setState所设置的值,它们的state都是基于最初的state的。 那么这个问题如何解决呢? 其实`setState`还有第二种形式,使用回调函数而非对象的形式去更新`state`,像这样 ``` this.setState((prevState,props)=>({counter:prevState.counter + Math.random()})); this.setState((prevState,props)=>({counter:prevState.counter + props.increment})) ``` 值得一提的是这种写法是 `setState(updater[, callback])`的语法糖形式,最终仍然会编译成这样执行。 ## component和事件 ### 和原生事件的区别 - React事件命名采用的是驼峰式而非原生中的小写形式 - 原生绑定时传递的是一个字符串,而react则是利用{} ``` //原生 <button onclick="activateLasers()"> Activate Lasers </button> //React <button onClick={activateLasers}> Activate Lasers </button> ``` - react中的ev对象是经过react封装过的 - 不支持return false ``` function ActionLink() { function handleClick(e) { //这个event不是原生的,而是自己封装的,故不存在兼容问题 e.preventDefault(); console.log('The link was clicked.'); } return ( <a href="#" onClick={handleClick}> Click me </a> ); } ``` ### this 如果是用类的形式定义的组件,我们需要注意事件函数中this的指向问题。 在react中我们不需要手动使用`addEventListener`对元素进行事件函数的绑定,只需要在`JSX tag`上像原生行内式绑定事件函数一样注册事件即可。 但这存在一个问题,react并不会帮我们把回调中的this指向组件实例,**甚至这个this也不是指向那个应该绑定的DOM元素**。 我们是希望这个`this`指向组件实例的,这样我们能拿到挂载在这个组件实例上的`state`。而改变回调this指向的方式大概有三种。 - 通过bind绑定事件处理函数 - 通过箭头函数包装事件处理函数 - 通过ES7初始值语法创建来创建事件处理函数 我们推荐第三种,十分方便 ``` handleClick = ()=>{ this.setState(...); } ``` ## 表单 ### state和受控组件 正常来说当我们在一个input输入后会立刻得到相应的显示在界面上。 但React允许我们在输入后,先把输入的信息hold住,进行一些处理后再显示在界面上。 这是怎么做到的呢?这就是通过我们之前所说的`React state`。 首先我要让一个input的value等于一个`state` ``` <input type="text" onChange={this.handleChange.bind(this,'username')} value={this.state.username}/> ``` 我们知道一个state在react中只能通过调用`setState`才会触发`render`导致重绘UI,这意味着只要我们不立刻调用setState,那么input的值就不会立刻改变。 在上面的示例中我们通过给input绑定一个事件,来对**输入**进行处理 ``` handleChange = (key,event)=>{ let val = event.target.value; this.setState({[key]:val}) } ``` 这样我们就完成了对表单输入的**截获**,使其受到了我们的控制,我们将这样的表单元素称之为受控组件。 --- 有一点要注意我们在上栗用到了es6的`computed property`语法 ``` setState({[key]:val}) ``` 其中`{[key]:val}`就相当于`let o={};o[key]=val`,这种语法允许我们在设置对象的键名时也能使用变量。 我们之所以要在事件处理函数中传递一个key过去,是因为一张表里可能有很多表单元素,每一个都对应我们组件中的一个state,而这个key就是用来区分他们的。 #### value = {null} 表示不再是受控组件 #### 关于select ``` <select> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option selected value="coconut">Coconut</option> <option value="mango">Mango</option> </select> ``` 不推荐以上,推荐在select里写value ``` <select value={this.state.value} onChange={this.handleChange}> <option value="grapefruit">Grapefruit</option> <option value="lime">Lime</option> <option value="coconut">Coconut</option> <option value="mango">Mango</option> </select> ``` 多选 ``` <select multiple={true} value={['a', 'b']}> ``` #### 关于file input=file 是不会受到react控制的 ### ref和非受控组件 我们在受控组件中能通过绑定事件处理函数的形式拿到组件中的表单元素。 但是像`<input type="file">`这种非受控组件,我们如果还想要截获它的值怎么截获到呢? 首先我们不可能像其它表单元素一样在它自个儿身上绑定事件处理函数,因为对于`file`,只有form表单提交时我们才能拿到file的值, so我们只能在form上绑定事件处理函数,那怎么拿到form中的file元素呢? 这就需要利用到`React ref`了 ``` <input type="text" ref="username" /> // 处理函数中 let username = this.refs.username.value ``` 以上写法ref=一个字符串已经被废弃 现在推荐这么写 ``` <input type="text" ref={input=>this.username=input} /> //input就是渲染出的真实dom ``` 可以发现,这里的ref里对应的是一个函数,此函数会在当此虚拟DOM转成真实DOM并插入到页面之后立刻调用,参数接收到的就是插入的真实dom >更新:16.3新特のReact.createRef() ![](https://box.kancloud.cn/1c142129466e9b328b5c31b550a67986_570x423.png) ## 列表和keys 记得我们说过,`JSX tag`可以作为函数返回值 So,我们能够这样渲染一个列表 ``` let array = [1,2,3,4,5]; let lists = array.map((item,index)=><li key={index}>{item}</li>); ReactDOM.render(( <ul> {lists} </ul> ), document.getElementById('root')); ``` 注意上栗中我们给每个li都绑定了一个key,这个key是数组中的索引位置,是独一无二的。 ### key和重绘 如果我们不给li绑定key,React会报一个警告, > Warning: Each child in an array or iterator should have a unique "key" prop. 但其实它已经自动帮我们加上key了。 为什么react渲染列表的时需要一个key呢? 记得我们上面说过react重绘时不会将整个`React Element`都重绘的吗。 嗯。。。那它是怎么做到的呢?就是利用这个key了,如果没有这个key,它是无法区别element中的tag谁是谁的。 ### li 和 `<ListItem>` 有些时候一个li内的内容过于复杂,我们会将其封装成一个组件 这个时候我们推荐把key挂载这个li的组件上,而不是组件内部返回的li上 > 以下示例出资React文档 ``` unction NumberList(props) { const numbers = props.numbers; const listItems = numbers.map((number) => // Correct! Key should be specified inside the array. <ListItem key={number.toString()} value={number} /> ); ``` ### key值无法获取 如果我们在组件上传递了一个key值,这个key值并不会被包装进`props`对象 ### 不推荐用索引作为key > 详见[ in-depth explanation on the negative impacts of using an index as a key.](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318) --- 参考: - [React官方文档](https://reactjs.org/docs/hello-world.html) --: ToBeContinue...