十、列表渲染
- map()方法、key
使用 map() 方法遍历数组
组件接收数组参数,每个列表元素分配一个 key,不然会出现警告 a key should be provided for list items,意思就是需要包含 key:
Keys 可以在 DOM 中的某些元素被增加或删除的时候帮助 React 识别哪些元素发生了变化。因此你应当给数组中的每一个元素赋予一个确定的标识。
一个元素的 key 最好是这个元素在列表中拥有的一个独一无二的字符串。通常,我们使用来自数据的id作为元素的 key
当元素没有确定的 id 时,你可以使用他的序列号索引index 作为key
如果列表可以重新排序,我们不建议使用索引来进行排序,因为这会导致渲染变得很慢。
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './05_list/01App_map' // 边遍历边渲染
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
src/05_list/01_App_map.jsx
// src/05_list/01App_map.jsx
import React, { Component } from 'react';
class App extends Component {
state = {
list: ['aa', 'bb', 'cc', 'dd']
}
render() {
return (
<div>
{
this.state.list.map((item, index) => {
return (
<div key={ item }>
{ item }
<button onClick={() => {
const arr = this.state.list
arr.splice(index, 1)
this.setState({
list: arr
})
}}>delete</button>
</div>
)
})
}
</div>
);
}
}
export default App;
接口
http://121.89.205.189:3000/api/city/sortCity实现多层遍历
src/index.js// src/index.js import React from 'react' import ReactDOM from 'react-dom/client' import ErrorBoundary from './ErrorBoundary'; // import App from './05_list/01App_map' // 边遍历边渲染 import App from './05_list/02App_mutiple_map' // 多层遍历 const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <ErrorBoundary> <App /> </ErrorBoundary> );
src/05_list/02_App_mutiple_map.jsx// src/05_list/02App_mutiple_map.jsx import React, { Component } from 'react'; class App extends Component { state = { cityList: [] } componentDidMount () { // fetch 数据请求基于promise 的原生的数据请求方式 // fetch()得到的是 基于promise对象的数据,需要转化为 json的数据格式,也是基于promise的 // fetch().then(res => res.json()) // fetch().then(res => res.json()).then(res => console.log(res)) fetch('http://121.89.205.189:3000/api/city/sortCity') .then(res => res.json()) .then(res => { console.log(JSON.parse(res.data)) this.setState({ cityList: JSON.parse(res.data) }) }) } render() { return ( <div> <ul> { this.state.cityList && this.state.cityList.map((item => { return ( <li key = { item.letter }> { item.letter } <ul> { item.data.length > 0 ? item.data.map((itm) => { return ( <li key={ itm.cityId }>{ itm.name }</li> ) }) : <li >暂无城市</li> } </ul> </li> ) })) } </ul> </div> ); } } export default App;如果遇到先遍历 后渲染
// src/index.js import React from 'react' import ReactDOM from 'react-dom/client' import ErrorBoundary from './ErrorBoundary'; // import App from './05_list/01App_map' // 边遍历边渲染 // import App from './05_list/02App_mutiple_map' // 多层遍历 import App from './05_list/03App_for' // 先遍历 后 渲染 const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <ErrorBoundary> <App /> </ErrorBoundary> ); // src/05_list/03App_for.jsx import React, { Component } from 'react'; class App extends Component { state = { list: ['1111', '2222', '33333', '44444'] } render() { const arr = [<p key="a">aa</p>, <p key="b">bb</p>] const newArr = [] // for (var item of this.state.list) { // newArr.push(<p key = { item }>{item}</p>) // } // for (var i in this.state.list) { // newArr.push(<p key = { i }>{ this.state.list[i] }</p>) // } this.state.list.forEach((item, index) => { newArr.push(<p key = { index }>{ item }</p>) }) return ( <div> { arr } { newArr } </div> ); } } export default App;
十一、表单绑定
在 React 里,HTML 表单元素的工作方式和其他的 DOM 元素有些不同,这是因为表单元素通常会保持一些内部的 state。例如这个纯 HTML 表单只接受一个名称:
<form>
<label>
名字:
<input type="text" name="name" />
</label>
<input type="submit" value="提交" />
</form>
此表单具有默认的 HTML 表单行为,即在用户提交表单后浏览到新页面。如果你在 React 中执行相同的代码,它依然有效。但大多数情况下,使用 JavaScript 函数可以很方便的处理表单的提交, 同时还可以访问用户填写的表单数据。实现这种效果的标准方式是使用“受控组件”。
表单元素的value值受 state的控制
11.1 各种表单的绑定与取值
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
import App from './06_form/01App_form' // 受控组件的写法
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
<App />
</ErrorBoundary>
);
src/06_form/01App_form.jsx
// src/06_form/01App_form.jsx
import React, { Component } from 'react';
class App extends Component {
state = {
userName: '',
password: '',
sex: '女',
lesson: '全端',
note: '',
hobby: [],
flag: false
}
// 一个表单写一个事件
// changeUserName = (event) => {
// this.setState({
// userName: event.target.value
// })
// }
// changePassword = (event) => {
// this.setState({
// password: event.target.value
// })
// }
// 利用表单的 name 的属性
changeInfo = (event) => {
console.log(event.target.name, event.target.value)
this.setState({ // [event.target.name] 对象key值如果是变量,则需要使用[]包裹
[event.target.name]: event.target.value
})
}
changeHobby = (event) => {
console.log(event.target.checked, event.target.value)
if (event.target.checked) { // 当前的选项从未选中到已选中,数组插入数据
const arr = this.state.hobby
arr.push(event.target.value)
this.setState({
hobby: arr
})
} else { // 当前的选项从选中到未选中, 数组需要删除数据
const arr = this.state.hobby
const index = arr.findIndex(item => item === event.target.value)
arr.splice(index, 1)
this.setState({
hobby: arr
})
}
}
changeFlag = (event) => {
this.setState({
flag: event.target.checked
})
}
render() {
// vue中可以使用template标签作为空标签,react中可以使用 <></> 或者 <React.Fragment></React.Fragment>
return (
<>
{/* <div>
用户名:
<input type='text' value = { this.state.userName } onChange={ this.changeUserName }/> { this.state.userName }
</div>
<div>
密码:
<input type='password' value = { this.state.password } onChange={ this.changePassword }/> { this.state.password }
</div> */}
<div>
用户名:
<input type='text' name='userName' value = { this.state.userName } onChange={ this.changeInfo }/> { this.state.userName }
</div>
<div>
密码:
<input type='password' name="password" value = { this.state.password } onChange={ this.changeInfo }/> { this.state.password }
</div>
<div>
性别:
<input type="radio" name="sex" value="男" onChange={ this.changeInfo }/>男
<input type="radio" name="sex" value="女" onChange={ this.changeInfo }/>女 -- { this.state.sex }
</div>
<div>
下拉选择:
<select name="lesson" value={ this.state.lesson } onChange={ this.changeInfo }>
<option value="鸿蒙">鸿蒙</option>
<option value="前端">前端</option>
<option value="全端">全端</option>
<option value="java">java</option>
</select> - { this.state.lesson }
</div>
<div>
爱好:
<input type="checkbox" name='hobby' value="篮球" onChange={ this.changeHobby }/>🏀
<input type="checkbox" name='hobby' value="足球" onChange={ this.changeHobby } />⚽
<input type="checkbox" name='hobby' value="网球" onChange={ this.changeHobby }/>🎾
-
{
this.state.hobby && this.state.hobby.map(item => {
return (<p key = { item }>{ item }</p>)
})
}
</div>
<div>
备注:
<textarea name='note' value = { this.state.note } onChange={ this.changeInfo }></textarea> - { this.state.note }
</div>
<div>
<input type="checkbox" checked={ this.state.flag } onChange={ this.changeFlag }/> 同意******协议 - { this.state.flag + '' }
</div>
</>
);
}
}
export default App;
11.2 受控表单以及受控组件
在 HTML 中,表单元素(如<input>、 <textarea> 和 <select>)之类的表单元素通常自己维护 state,并根据用户输入进行更新。而在 React 中,可变状态(mutable state)通常保存在组件的 state 属性中,并且只能通过使用 setState()来更新。
我们可以把两者结合起来,使 React 的 state 成为“唯一数据源”。渲染表单的 React 组件还控制着用户输入过程中表单发生的操作。被 React 以这种方式控制取值的表单输入元素就叫做“受控组件”。
input、textarea、select受控组件: value的属性受了state的控制
- 使用了受控组件,一定要写
value属性以及onChange事件
radio、'checkbox' 受控组件: checked 的属性受了state的控制如果需要设置默认值,那么需要通过
defaultValue以及defaultChecked设置
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
import ErrorBoundary from './ErrorBoundary';
// import App from './06_form/01App_form' // 受控组件的写法
import App from './06_form/02App_form_ref' // 非受控组件的写法
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ErrorBoundary>
{/* 开启react的严格模式 */}
<React.StrictMode>
<App />
</React.StrictMode>
</ErrorBoundary>
);
// src/06_form/02App_form_ref.jsx
import React, { Component } from 'react';
class App extends Component {
// 因为 ref 的string类型的写法要被废除,类组件推荐使用createRef代替 --- 严格模式下
passwordRef = React.createRef()
state = {
userName: '',
password: ''
}
// 一个表单写一个事件
changeUserName = () => {
console.log(this.refs.userName.value)
this.setState({
userName: this.refs.userName.value
})
}
changePassword = () => {
console.log(this.passwordRef.current.value)
this.setState({
password: this.passwordRef.current.value
})
}
render() {
// vue中可以使用template标签作为空标签,react中可以使用 <></> 或者 <React.Fragment></React.Fragment>
// 可以通过ref属性 实现 类似DOM查找节点
// ref的字符串类型要被废除,开启严格模式控制台就会有提示(src/index.js React.StrictMode)
// 以后在类组件中最好使用 createRef 代替
return (
<>
<div>
用户名:
<input type='text' ref="userName" onChange={ this.changeUserName }/> { this.state.userName }
</div>
<div>
密码:
{/* ref 属性 使用 createRef代替字符串写法 */}
<input type='password' ref={ this.passwordRef } onChange={ this.changePassword }/> { this.state.password }
</div>
</>
);
}
}
export default App;
十二、状态提升
在 React 中,将多个组件中需要共享的 state 向上移动到它们的最近共同父组件中,便可实现共享 state。这就是所谓的“状态提升”。
12.1 非父子组件通信
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
// import App from './01_props/01Parent_Child'
// import App from './01_props/02Parent_Child_value'
// import App from './01_props/03Parent_Child_default'
// import App from './01_props/04Parent_Child_type'
// import App from './01_props/05App_props_children'
// import App from './01_props/06_App_multiple_props_children'
// import App from './01_props/07App_mouse_tracker'
// import App from './01_props/08App_render_props'
// import App from './02_state/01App_state_es6'
// import App from './02_state/02App_state_es7'
// import App from './02_state/03App_setState_function'
// import App from './02_state/04App_setState_object'
// import App from './02_state/05App_computed'
// import App from './02_state/06App_lifeCycle'
// import App from './02_state/07App_PureComponent'
// import App from './03_event/01App_event_es5'
// import App from './03_event/02App_event_es6'
// import App from './04_condition/01App_condition_yu'
// import App from './04_condition/02App_condition_san'
// import App from './04_condition/03App_condition_classnames'
// import App from './04_condition/04App_condition_cssinjs'
// import App from './04_condition/05App_style'
// import App from './04_condition/06App_module_css'
// import App from './05_list/01App_map'
// import App from './05_list/02App_mutiple_map'
// import App from './06_form/01App_form copy'
import App from './07_state_up/01App_parent_child_value'
import ErrorBoundary from './ErrorBoundary'
// import './style.stylus'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<ErrorBoundary>
<App root={ root }/>
</ErrorBoundary>
)
src/07_state_up/01App_parent_child_value.jsx
// src/07_state_up/01App_parent_child_value.jsx
import React, { Component } from 'react';
class Child1 extends Component {
state = {
count: 10
}
render () {
return (
<div>
<button onClick={ () => this.setState({ count: this.state.count + 1 }) }>加1</button>{this.state.count}
</div>
)
}
}
class Child2 extends Component {
state = {
count: 10
}
render () {
return (
<div>
<button onClick={ () => this.setState({ count: this.state.count + 1 }) }>加1</button>{this.state.count}
</div>
)
}
}
class App extends Component {
render() {
return (
<div>
<Child1 />
<hr />
<Child2 />
</div>
);
}
}
export default App;
我们发现Child1和Child2都是两个独立的个体,并没有实现数据共享
src/index.js
// src/index.js
import React from 'react'
import ReactDOM from 'react-dom/client'
// import App from './01_props/01Parent_Child'
// import App from './01_props/02Parent_Child_value'
// import App from './01_props/03Parent_Child_default'
// import App from './01_props/04Parent_Child_type'
// import App from './01_props/05App_props_children'
// import App from './01_props/06_App_multiple_props_children'
// import App from './01_props/07App_mouse_tracker'
// import App from './01_props/08App_render_props'
// import App from './02_state/01App_state_es6'
// import App from './02_state/02App_state_es7'
// import App from './02_state/03App_setState_function'
// import App from './02_state/04App_setState_object'
// import App from './02_state/05App_computed'
// import App from './02_state/06App_lifeCycle'
// import App from './02_state/07App_PureComponent'
// import App from './03_event/01App_event_es5'
// import App from './03_event/02App_event_es6'
// import App from './04_condition/01App_condition_yu'
// import App from './04_condition/02App_condition_san'
// import App from './04_condition/03App_condition_classnames'
// import App from './04_condition/04App_condition_cssinjs'
// import App from './04_condition/05App_style'
// import App from './04_condition/06App_module_css'
// import App from './05_list/01App_map'
// import App from './05_list/02App_mutiple_map'
// import App from './06_form/01App_form copy'
// import App from './07_state_up/01App_parent_child_value'
import App from './07_state_up/02App_state_up'
import ErrorBoundary from './ErrorBoundary'
// import './style.stylus'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<ErrorBoundary>
<App root={ root }/>
</ErrorBoundary>
)
src/07_state_up/02App_state_up.jsx
// src/07_state_up/02App_state_up.jsx
import React, { Component } from 'react';
class Child1 extends Component {
state = {
count: 10
}
render () {
return (
<div>
<button onClick={ this.props.onClick }>加1</button>{this.props.count}
</div>
)
}
}
class Child2 extends Component {
state = {
count: 10
}
render () {
return (
<div>
<button onClick={ this.props.onClick }>加1</button>{this.props.count}
</div>
)
}
}
class App extends Component {
state = {
count: 100
}
add = () => {
this.setState({
count: this.state.count + 1
})
}
render() {
return (
<div>
<Child1 count = { this.state.count } onClick = { this.add } />
<hr />
<Child2 count = { this.state.count } onClick = { this.add } />
</div>
);
}
}
export default App;
12.2 状态提升解读
实现方式是 利用最近的共同的父级组件中,用props的方式传过去到两个子组件,props中传的是一个setState的方法,通过子组件触发props传过去的方法,进而调用父级组件的setState的方法,改变了父级组件的state,调用父级组件的add方法,进而同时改变了两个子级组件的数据。
这是 两个有关连的同级组件的传值,因为react的单项数据流,所以不在两个组件中进行传值,而是提升到 最近的共同的父级组件中,改变父级的state,进而影响了两个子级组件的render。
注意如果两个组件是同级组件(这两个组件的父组件是同一个)才考虑状态提升共享数据