05-React组件的组合使用

05-React组件的组合使用


1.TodoList案例

需求:TodoList组件化实现此功能

  1. 显示所有todo列表
  2. 输入文本, 点击按钮显示到列表的首位, 并清除输入的文本

05-React组件的组合使用_第1张图片

1).实现:

  1. 完成TodoList组件的静态页面以及拆解组件

    05-React组件的组合使用_第2张图片 05-React组件的组合使用_第3张图片
  2. 动态初始化列表

    //App.jsx
    export default class App extends Component {
      // 初始化状态
      state = {
        todos: [
          { id: '001', name: '吃饭', done: true },
          { id: '002', name: '睡觉', done: true },
          { id: '003', name: '敲代码', done: false },
          { id: '004', name: '逛街', done: true },
        ]
      }
      render() {
        const { todos } = this.state
        return (
          
    ) } }
    //List.jsx
    export default class List extends Component {
      render() {
       // 接收来自App父组件传递的数据   
        const { todos } = this.props
        return (
          
      {todos.map(todo => { return })}
    ) } }
    //Item.jsx
    export default class Item extends Component {
      render() {
          // 接收来自List父组件传递的数据   
        const { id, name, done } = this.props
        return (
          
  3. ) } }

    05-React组件的组合使用_第4张图片

  4. 完成添加“任务”的功能(父子组件之间传值)

    在父组件中创建一个添加数据的方法addTodo,然后将方法传递给子组件,当子组件向该方法传递了参数后,父组件便可以接收到子组件传递的参数

    //App.jsx
    export default class App extends Component {
      // 初始化状态
      state = {
        todos: [
          { id: '001', name: '吃饭', done: true },
          { id: '002', name: '睡觉', done: true },
          { id: '003', name: '敲代码', done: false },
          { id: '004', name: '逛街', done: true },
        ]
      }
     //addTodo用于添加一个todo,接收的参数是todo对象
      addTodo = (todoObj) => {
        console.log('APP:' + data);
        // 获取原数组
        const { todos } = this.state
        // 追加一个新数组
        const newTodos = [todoObj, ...todos]
        // 更新状态
        this.setState({ todos: newTodos })
      }   
      render() {
        const { todos } = this.state
        return (
          
    ) } }

    子组件Header创建一个点击回车键回调的方法handlerKeyUp,在该方法中向从父组件接收到的方法addTodo中传递参数,将新添加的数据反馈给父组件

    //Header.jsx
    export default class Header extends Component {
      // 键盘事件的回调
      handlerKeyUp = (event) => {
        // keyCode 触发事件的按键码
        // 解构赋值keyCode, target
        const { keyCode, target } = event
        // 判断是否是回车按键
        if (keyCode !== 13) return
        // 添加的todo名字不能为空
        if (target.value.trim() == '') {
          alert('输入不能为空')
          return
        }
        // 准备一个新的todo对象
        const todoObj = {
         // 使用nanoid库创建一个不重复的ID   
          id: nanoid(),
          name: target.value,
          done: false
        }
        this.props.addTodo(todoObj);
        // 清空输入
        target.value='';
      }
      render() {
        return (
          
    ) } }

    05-React组件的组合使用_第5张图片

  5. 完成删除"任务"的功能(祖孙组件之间的传值)

    在祖组件中创建一个删除数据的方法deleteTodo,然后将方法传递给子组件,再由子组件传递给孙组件,当孙组件向该方法传递了参数后,祖组件便可以接收到子组件传递的参数

    //App.jsx
    export default class App extends Component {
      // 初始化状态
      state = {
        todos: [
          { id: '001', name: '吃饭', done: true },
          { id: '002', name: '睡觉', done: true },
          { id: '003', name: '敲代码', done: false },
          { id: '004', name: '逛街', done: true },
        ]
      }
      // deleteTodo用于删除一个todo对象
      deleteTodo = (id) => {
        const { todos } = this.state
        // 删除指定id的todo对象
        const newTodos = todos.filter(todoObj => {
          return todoObj.id !== id
        })
        // 更新状态
        this.setState({ todos: newTodos })
      }
      render() {
        const { todos } = this.state
        return (
          
    ) } }

    List组件接收从父组件传递来的删除数据的方法deleteTodo,再将deleteTodo方法传递给自己的子组件Item

    //List.jsx
    export default class List extends Component {
      render() {
        const { todos, updateTodo} = this.props
        return (
          
      {todos.map(todo => { return })}
    ) } }

    Item组件创建一个删除“任务”的回调方法handleDelete,然后在该方法中调用deleteTodof方法,将需要删除的数据id传递给祖组件App

    //Item.jsx
    export default class Item extends Component {
      // 删除一个todo的回调
      handleDelete = (id) => {
        // console.log('通知App删除'+id);
        if (window.confirm('确定删除吗?')) {
          this.props.deleteTodo(id)
        }
      }
      render() {
        const { id, name, done } = this.props
        const { mouse } = this.state
        return (
          
  6. ) } }

    05-React组件的组合使用_第6张图片

  7. 为每个Item组件添加鼠标移入高亮且删除按钮显现、鼠标移出无高亮显示且删除按钮隐藏的效果

    //Item.jsx
    export default class Item extends Component {
        state = {
            mouse: false //标识鼠标移入、移出
        }
        //鼠标移入、移出的回调
        handleMouse = (flag) => {
            return () => {
                this.setState({ mouse: flag })
            }
        }
        // 删除一个todo的回调
        handleDelete = (id) => {
            if (window.confirm('确定删除吗?')) {
                this.props.deleteTodo(id)
            }
        }
        render() {
            const { id, name, done } = this.props
            const { mouse } = this.state
            return (
                
  8. ) } }

    05-React组件的组合使用_第7张图片

  9. 修改Item组件勾选框的状态和修改数据(祖孙组件之间的传值)

    在祖组件中创建一个更新数据的方法updateTodo,然后将方法传递给子组件,再由子组件传递给孙组件,当孙组件向该方法传递了参数后,祖组件便可以接收到子组件传递的参数

    //App.jsx
    export default class App extends Component {
      // 初始化状态
      state = {
        todos: [
          { id: '001', name: '吃饭', done: true },
          { id: '002', name: '睡觉', done: true },
          { id: '003', name: '敲代码', done: false },
          { id: '004', name: '逛街', done: true },
        ]
      }
      // updateTodo用于更新一个todo对象
      updateTodo = (id, done) => {
        // 获取状态中的todos
        const { todos } = this.state
        // 匹配处理数据
        const newTodos = todos.map(todoObj => {
          if (todoObj.id === id) return { ...todoObj, done }
          else return todoObj
        })
        this.setState({ todos: newTodos })
      }
      render() {
        const { todos } = this.state
        return (
          
    ) } }

    List组件接收从父组件传递来的删除数据的方法updateTodo,再将updateTodo方法传递给自己的子组件Item

    //List.jsx
    export default class List extends Component {
      render() {
        const { todos, updateTodo, deleteTodo } = this.props
        return (
          
      {todos.map(todo => { return })}
    ) } }

    Item组件创建一个更新数据的回调方法handleDelete,然后在该方法中调用updateTodo方法,将需要更新的数据id传递给祖组件App

    export default class Item extends Component {
        state = {
            mouse: false //标识鼠标移入、移出
        }
        //鼠标移入、移出的回调
        handleMouse = (flag) => {
            return () => {
                this.setState({ mouse: flag })
                // console.log(flag);
            }
        }
        //勾选、取消勾选某一个todo的回调
        handleChange = (id) => {
            return (event) => {
                // console.log(id,event.target.checked);
                this.props.updateTodo(id, event.target.checked);
            }
        }
        // 删除一个todo的回调
        handleDelete = (id) => {
            // console.log('通知App删除'+id);
            if (window.confirm('确定删除吗?')) {
                this.props.deleteTodo(id)
            }
        }
        render() {
            const { id, name, done } = this.props
            const { mouse } = this.state
            return (
                
  10. ) } }

    05-React组件的组合使用_第8张图片

    05-React组件的组合使用_第9张图片

  11. 完成底部组件全选修改数据的功能

    //APP.jsx
    export default class App extends Component {
        // 初始化状态
        state = {
            todos: [
                { id: '001', name: '吃饭', done: true },
                { id: '002', name: '睡觉', done: true },
                { id: '003', name: '敲代码', done: false },
                { id: '004', name: '逛街', done: true },
            ]
        }
        // checkAllTodo用于全选
        checkAllTodo=(done)=>{
            // 获取原来的todos
            const {todos}=this.state
            // 处理数据
            const newTodos=todos.map(todoObj=>{
                return {...todoObj,done}
            })
            // 更新数据
            this.setState({todos:newTodos})
        }
        render() {
            const { todos } = this.state
            return (
                
    ) } }
    //Footer.jsx
    export default class Footer extends Component {
      handleChange=(event)=>{
        this.props.checkAllTodo(event.target.checked)
      }
      render() {
        const {todos}=this.props
        // 已完成的个数
        const doneCount=todos.reduce((pre,todo)=>pre+(todo.done?1:0),0)
        // 总数
        const total=todos.length
        return (
          
    已完成{doneCount} / 全部{total}
    ) } }

    05-React组件的组合使用_第10张图片

  12. 完成“清楚已完成任务”按钮的功能

    export default class App extends Component {
      // 初始化状态
      state = {
        todos: [
          { id: '001', name: '吃饭', done: true },
          { id: '002', name: '睡觉', done: true },
          { id: '003', name: '敲代码', done: false },
          { id: '004', name: '逛街', done: true },
        ]
      }
      // clearAllDone用于清除所有已完成的
      clearAllDone=()=>{
        // 获取原来的todos
        const {todos}=this.state
        // 处理数据
        const newTodos= todos.filter(todoObj=>{
          return !todoObj.done
        })
        // 更新状态
        this.setState({todos:newTodos})
      }
      render() {
        const { todos } = this.state
        return (
          
    ) } }
    export default class Footer extends Component {
      handleClearAllDone=()=>{
        this.props.clearAllDone()
      }
      render() {
        const {todos}=this.props
        // 已完成的个数
        const doneCount=todos.reduce((pre,todo)=>pre+(todo.done?1:0),0)
        // 总数
        const total=todos.length
        return (
          
    已完成{doneCount} / 全部{total}
    ) } }

    05-React组件的组合使用_第11张图片

2).总结:

todoList案例相关知识点:

  1. 拆分组件、实现静态组件,注意:className、style的写法
  2. 动态初始化列表,如何确定将数据放在哪个组件的state中?
    1. 某个组件使用:放在其自身的state中
    2. 某些组件使用:放在他们共同的父组件state中(官方称此操作为:状态提升)
  3. 关于父子之间通信:
    1. 【父组件】给【子组件】传递数据:通过props传递
    2. 【子组件】给【父组件】传递数据:通过props传递,要求父提前给子传递一个函数
  4. 注意defaultChecked和checked的区别,类似的还有:defaultValue和value
  5. 状态在哪里,操作状态的方法就在哪里

2.GitHub搜索案例

效果:

05-React组件的组合使用_第12张图片

1).实现:

a.使用axios发送请求
  1. 搭建组件静态效果

    //List.jsx
    export default class List extends Component {
        render() {
            return (
                

    {user.login}

    ) } }
    //Search.jsx
    export default class Search extends Component {
      render() {
        return (
          

    搜索Github用户

     
    ) } }
  2. 配置脚手架代理(配置代理)

    const { createProxyMiddleware } = require('http-proxy-middleware');
    module.exports = function (app) {
        app.use(
            createProxyMiddleware('/api1', {    
                target: 'http://localhost:5000',   
                secure: false,  //
                changeOrigin: true,    
                pathRewrite: {
                    '^/api1': '',
                },  
            })
        )
    }
    
  3. 完成搜索框的搜索和发送请求的功能

    1. 收集Search组件输入框的内容

      export default class Search extends Component {
          Search = () => {
              // 连续解构赋值+重命名
              const { KeyWordElement: { value: keyWord } } = this;
              console.log(keyWord);
          }
          render() {
              return (
                  

      搜索Github用户

      this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" /> 
      ) } }

      05-React组件的组合使用_第13张图片

    2. 发送请求

      export default class Search extends Component {
          Search = () => {
              const { KeyWordElement: { value: keyWord } } = this;
              // 发送请求
              axios.get(`/api1/search/users2?q=${keyWord}`).then(
                  response => { 
                      console.log(response.data.items);
                  },
                  error => { 
                      console.log('失败了', error); 
                  }
              )
          }
          render() {
              return (
                  

      搜索Github用户

      this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" /> 
      ) } }

      05-React组件的组合使用_第14张图片

  4. 动态初始化组件的状态

    //App.jsx
    export default class App extends Component {
        // 初始化状态
        state={
            users:[],//users初始值为数组
            isFirst:true,//是否为第一次打开页面
            isLoading:false,//标识是否处于加载中
            err:""// 存储请求相关的错误信息
        }
        render() {
            const {users}=this.state
            return (
                
    ) } }
    //List.jsx
    export default class List extends Component {
        render() {
            const { users,isFirst,isLoading,err } = this.props
            return (
                
    { //使用三元运算符动态显示组件的内容 isFirst?

    欢迎使用,输入关键字,随后点击搜索

    : isLoading?

    Loading......

    : err?

    {err}

    : users.map(user => { return (

    {user.login}

    ) }) }
    ) } }
  5. 发起请求并将数据显示到组件上

    //App.jsx
    export default class App extends Component {
        // 初始化状态
        state={
            users:[],//users初始值为数组
            isFirst:true,//是否为第一次打开页面
            isLoading:false,//标识是否处于加载中
            err:""// 存储请求相关的错误信息
        }
        // 更新App的state
        updateAppState=(stateObj)=>{
            this.setState(stateObj)
        }
        render() {
            const {users}=this.state
            return (
                
    ) } }
    //Search.jsx
    export default class Search extends Component {
      Search = () => {
        // 连续解构赋值+重命名
        const { KeyWordElement: { value: keyWord } } = this;
        //  发送请求前通知App更新状态
        this.props.updateAppState({ isFirst:false,isLoading:true,})
        // 发送请求
        axios.get(`/api1/search/users2?q=${keyWord}`).then(
          response => { 
            //请求成功后通知App更新状态
            this.props.updateAppState({isLoading:false,users:response.data.items})
          },
          error => { 
            console.log('失败了', error); 
            // 请求成功后通知App更新状态
            this.props.updateAppState({isLoading:false,err:error.message})
          }
        )
      }
      render() {
        return (
          

    搜索Github用户

    this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" /> 
    ) } }
b.使用PubSub实现(兄弟组件之间传值)

消息订阅-发布机制

  1. 工具库: PubSubJS

  2. 下载:

    npm install pubsub-js --save
    
  3. 使用:

    1. import PubSub from 'pubsub-js' //引入
      
    2. PubSub.subscribe('delete', function(data){ }); //订阅
      
    3. PubSub.publish('delete', data) //发布消息
      
    4. PubSub.unsubscribe(this.xxx)
      
  4. 初始化各个组件

    //App.jsx
    export default class App extends Component {
        render() {
            return (
                
    ) } } //Search.jsx export default class Search extends Component { render() { return (

    搜索Github用户

     
    ) } } //List.jsx export default class List extends Component { render() { return (

    {user.login}

    ) } }
  5. 初始化组件的状态

    //List.jsx
    export default class List extends Component {
      // 初始化状态
      state = {
        users: [],
        isFirst: true,
        isLoading: false,
        err: ""
      }
      render() {
        const { users, isFirst, isLoading, err } = this.state
        return (
          
    { isFirst ?

    欢迎使用,输入关键字,随后点击搜索

    : isLoading ?

    Loading......

    : err ?

    {err}

    : users.map(user => { return (

    {user.login}

    ) }) }
    ) } }
  6. 发布消息与发送请求数据

    //Search.jsx
    export default class Search extends Component {
      Search = () => {
        const { KeyWordElement: { value: keyWord } } = this;
        PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
        axios.get(`/api1/search/users2?q=${keyWord}`).then(
          response => { 
            PubSub.publish('Spongebob',{isLoading:false,users:response.data.items})
          },
          error => { 
            PubSub.publish('Spongebob',{isLoading:false,err:error.message})
          }
        )
      }
      render() {
        return (
          

    搜索Github用户

    this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" /> 
    ) } }
  7. 订阅消息与动态更新数据

    export default class List extends Component {
      // 初始化状态
      state = {
        users: [],
        isFirst: true,
        isLoading: false,
        err: ""
      }
      componentDidMount() {
        this.token = PubSub.subscribe('Spongebob', (_, stateObj) => {
          this.setState(stateObj)
        })
      }
      componentWillUnmount() {
       // 组件销毁后取消订阅消息   
        PubSub.unsubscribe(this.token)
      }
      render() {
        const { users, isFirst, isLoading, err } = this.state
        return (
          
    { isFirst ?

    欢迎使用,输入关键字,随后点击搜索

    : isLoading ?

    Loading......

    : err ?

    {err}

    : users.map(user => { return (

    {user.login}

    ) }) }
    ) } }
c.使用fetch发送请求

Fetch

特点:

  1. fetch: 原生函数,不再使用XmlHttpRequest对象提交ajax请求
  2. 老版本浏览器可能不支持
export default class Search extends Component {
    Search = async() => {
        const { KeyWordElement: { value: keyWord } } = this;
        PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
        // 发送请求
        try {
            const response=await fetch(`/api1/search/users2?q=${keyWord}`);
            PubSub.publish('Spongebob',{isFirst:false,isLoading:true})
            const result=await response.json();
            // PubSub.publish('Spongebob',{isLoading:false,users:result})
            console.log(result);
        } catch (error) {
            console.log('请求出错',error);
        }
    }
    render() {
        return (
            

搜索Github用户

this.KeyWordElement = c} type="text" placeholder="输入关键词点击搜索" /> 
) } }

2).总结:

github搜索案例相关知识点:

  1. 设计状态时要考虑全面,例如带有网络请求的组件,要考虑请求失败怎么办。

  2. ES6小知识点:解构赋值+重命名

    let obj {a:{b:1}}
    //传统解构赋值
    const{a}=obj;
    //连续解构赋值
    const{a:(b}}=obj;
    //连续解构赋值+重命名
    const{a:{b:value}}=obj;
    
  3. 消息订阅与发布机制

    1. 先订阅,再发布(理解:有一种隔空对话的感觉)
    2. 适用于任意组件间通信
    3. 要在组件的componentwillUnmount中取消订阅
  4. fetch发送请求(关注分离的设计思想)

    try{
        const response=await fetch(/api1/search/users2?q=${keyWord))
        const data await response.json()
        console.log(data);
    }catch (error){
        console.1og('请求出错',error);
    }
    

你可能感兴趣的:(React自学,react.js,javascript,前端)