native-base checkbox 不能多选,选项点击错乱

409 阅读2分钟

前言

我在使用native-base的checkbox组件时,发现一个问题,在issues查看说需要降版本还是存在同样的问题,

ezgif-4-f1cf74bd62.gif

所以打算自己实现一个checkboxgroup组件

使用@react-stately来实现

react-stately是一个React Hooks库,为许多常见的组件提供状态管理,核心逻辑,并且是跨平台的

@react-stately和@react-native-aria在native-base库中有引入,不需要yarn安装,重写checkbox 逻辑就可以了

// Checkbox.js
import React, { useContext, useRef } from "react";
import { Pressable, View, Text } from "react-native";
import { useCheckbox, useCheckboxGroupItem } from "@react-native-aria/checkbox";
import { useToggleState } from "@react-stately/toggle";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import { CheckboxGroupContext } from "./CheckboxGroup";
// 多选
export function Checkbox(props) {
  let groupState = useContext(CheckboxGroupContext);
  let inputRef = useRef(null);
  let { inputProps } = groupState
    ? useCheckboxGroupItem(
        {
          ...props,
          isRequired: props.isRequired,
          validationState: props.validationState,
        },
        groupState,
        inputRef
      )
    : useCheckbox(props, useToggleState(props), inputRef);

  let icon = inputProps.checked ? "checkbox-marked" : "checkbox-blank-outline";

  const iconColor = props.isDisabled ? "#d1d1d1" : "#3b82f6";

  return (
    <Pressable {...inputProps}>
      <View style={{ flexDirection: "row", alignItems: "center" }}>
        <MaterialCommunityIcons size={30} color={iconColor} name={icon} />
        <Text>{props.children}</Text>
      </View>
    </Pressable>
  );
}
// CheckboxGroup.js
import React from "react";
import { useCheckboxGroupState } from "@react-stately/checkbox";
import { useCheckboxGroup } from "@react-native-aria/checkbox";
import { Text, View } from "react-native";

export let CheckboxGroupContext = React.createContext(null);

export function CheckboxGroup(props) {
  let { children, label } = props;
  let state = useCheckboxGroupState(props);
  let { groupProps, labelProps } = useCheckboxGroup(props, state);

  return (
    <View {...groupProps}>
      {label && <Text {...labelProps}>{label}</Text>}
      <CheckboxGroupContext.Provider value={state}>
        {children}
      </CheckboxGroupContext.Provider>
    </View>
  );
}

调用组件App.js

import React from "react";
import { CheckboxGroup } from "@/components/Checkbox/CheckboxGroup";
import { Checkbox } from "@/components/Checkbox/Checkbox";
import { Heading, HStack, VStack, Text, Box, View } from "native-base";

const Example = () => {
  const [groupValue, setGroupValue] = React.useState(["1"]);
  return (
    <Box alignItems="center">
      <VStack space={2}>
        <HStack alignItems="baseline">
          <Heading fontSize="lg">Hobbies</Heading>
        </HStack>
        <VStack>
          <Box>
            <Text>选择: ({groupValue.length})</Text>
          </Box>
        </VStack>
        <CheckboxGroup
          value={groupValue}
          accessibilityLabel="pick an item"
          onChange={setGroupValue}
        >
          <Checkbox value="1" isSelected>
            1
          </Checkbox>
          <Checkbox value="2" isDisabled>
            2
          </Checkbox>
          <Checkbox value="3">3</Checkbox>
          <Checkbox value="4">4</Checkbox>
        </CheckboxGroup>
      </VStack>
    </Box>
  );
};


export default Example;

自定义checkbox

  1. @/hooks/toggle/index.js 设置toggle 状态
import React from 'react'
export function useToggleState(props) {
  let { isReadOnly } = props;
  let [isSelected, setSelected] = React.useState(props.isSelected || false);

  function updateSelected(value) {
    if (!isReadOnly) {
      setSelected(value);
    }
  }

  function toggleState() {
    if (!isReadOnly) {
      setSelected(!isSelected);
    }
  }
  return {
    isSelected,
    setSelected: updateSelected,
    toggle: toggleState,
  };
}
  1. checkbox.js 创建Checkbox组件
import React, { useContext, useRef } from "react";
import { Pressable, View, Text } from "react-native";
import { useToggleState } from "@/hooks/toggle";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";

export function Checkbox(props) {
  let {isSelected, toggle} = useToggleState(props)
  let icon = isSelected ? "checkbox-marked" : "checkbox-blank-outline";
  const iconColor = props.isDisabled ? "#d1d1d1" : "#3b82f6";

  return (
    <Pressable onPress={toggle}>
      <View style={{ flexDirection: "row", alignItems: "center" }}>
        <MaterialCommunityIcons size={30} color={iconColor} name={icon} />
        <Text>{props.children}</Text>
      </View>
    </Pressable>
  );
}

  1. 调用,单个Checkbox可以切换状态
<Checkbox value="3">3</Checkbox>
  1. 加上checkboxGroup之后,简单实现一下
import React from "react";
import { View,Pressable,  VStack, Text, Box } from "native-base";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";

let CheckboxGroupContext = React.createContext(null);

function useCheckboxGroupState(props) {
  const {onChange, value} = props
  const [selectedValues, setselectedValues] = React.useState(value|| [])
  const result = {
    value: selectedValues,
    isSelected(value) {
      return selectedValues.includes(value);
    },
    toggleValue(value) {
      let cv = selectedValues.includes(value)
        ? selectedValues.filter(existingValue => existingValue !== value) 
        : selectedValues.concat(value)
      setselectedValues(cv);
      onChange(cv)
    }
  }
  return result
}
function useCheckboxGroupItem(props, groupState) {
  const {value, isDisabled} = props
  const isSelected = groupState.isSelected(value)

  return {
    onPress: (e) => {
      if (isDisabled) return
      groupState.toggleValue(value)
      if (props.onChange) {
        props.onChange(isSelected);
      }
    
    },
    checked: isSelected,
  }
}
function useCheckbox(props) {
  const {value, isDisabled} = props
  const [isSelected,setIsSelected] = React.useState(props.isSelected || false)
  return {
    onPress: (e) => {
      if (isDisabled) return
      setIsSelected(!isSelected)
      if (props.onChange) {
        props.onChange(isSelected);
      }
    
    },
    checked: isSelected,
  }
}
function CheckboxGroup(props) {
  let { children } = props;
  let state = useCheckboxGroupState(props);
  return (
      <CheckboxGroupContext.Provider value={state}>
        {children}
      </CheckboxGroupContext.Provider>
  );
}
function Checkbox(props) {
  const {isDisabled, children} = props
  let groupState = React.useContext(CheckboxGroupContext);
  let inputRef = React.useRef(null);
  let inputProps= groupState
  ? useCheckboxGroupItem(props, groupState, inputRef)
  : useCheckbox(props)

  let icon = inputProps.checked ? "checkbox-marked" : "checkbox-blank-outline";
  const iconColor = isDisabled ? "#d1d1d1" : "#3b82f6";
  return (
    <Pressable {...inputProps}>
      <View style={{ flexDirection: "row", alignItems: "center" }}>
        <MaterialCommunityIcons size={30} color={iconColor} name={icon} />
        <Text>{children}</Text>
      </View>
    </Pressable>
  )
}
export default function TestCheckBox() {
  const [groupValue, setGroupValue] = React.useState(["1"]);
  return (
    <Box>
      <VStack>
          <Box>
          <Text>选择: ({groupValue.length},{groupValue})</Text>
          </Box>
        </VStack>
        <CheckboxGroup
          value={groupValue}
          onChange={setGroupValue}
        >
          <Checkbox value="1" isSelected>
            1
          </Checkbox>
          <Checkbox value="2" isDisabled>
            2
          </Checkbox>
          <Checkbox value="3">3</Checkbox>
          <Checkbox value="4">4</Checkbox>
        </CheckboxGroup>
    </Box>
  )
}