React基础知识总结(三)

94 阅读9分钟

十、列表渲染

  • 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 以这种方式控制取值的表单输入元素就叫做“受控组件”。

inputtextareaselect 受控组件: 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

注意如果两个组件是同级组件(这两个组件的父组件是同一个)才考虑状态提升共享数据