一个独立的功能组件能否根据另一个组件的状态变化重新渲染?

Can an independent functional component re-render based on the state change of another?

我是 React Native 的新手,我的理解是功能组件和钩子是必经之路。我正在尝试做的事情已经归结为我能想到的最简单的情况,以用作示例。 (顺便说一句,我正在用 TypeScript 编写。)

我有两个独立的组件。两者之间没有父子关系。看看:

这两个组件是导航栏上的登录按钮和封闭屏幕中的开关。 如何让登录按钮在开关打开时启用,在开关关闭时禁用?

登录按钮如下所示:

const LoginButton = (): JSX.Element => {
  const navigation = useNavigation();

  const handleClick = () => {
    navigation.navigate('Away');
  };

  // I want the 'disabled' value to update based on the state of the switch.
  return (
    <Button title="Login"
            color="white"
            disabled={false}
            onPress={handleClick} />
  );
};

如您所见,我现在只是对按钮的 disabled 设置进行了硬编码。我想这无疑会变成动态的东西。

包含开关的屏幕如下所示:

const HomeScreen = () => {
  const [isEnabled, setEnabled] = useState(false);
  const toggleSwitch = () => setEnabled(value => !value);

  return (
    <SafeAreaView>
      <Switch
        style={styles.switch}
        ios_backgroundColor="#3e3e3e"
        onValueChange={toggleSwitch}
        value={isEnabled}
      />
    </SafeAreaView>
  );
};

让我陷入困境的是 HomeScreenLoginButton 在导航器堆栈中的设置方式如下。我想不出让一个人“知道”另一个人的方法:

<MainStack.Screen name="Home" 
                  component={HomeScreen} 
                  options={{title: "Home", headerRight: LoginButton}} />

我需要让登录按钮组件在开关状态发生变化时重新呈现,但我似乎无法触发它。我尝试应用几种不同的东西,都涉及某种钩子。我必须承认,我想我至少错过了大局,可能还错过了一些更精细的细节。

我愿意接受任何建议,但实际上我想知道最简单、最佳实践(或类似)的解决方案是什么。这可以纯粹用功能组件来完成吗?我必须在某处引入 class 吗?是否有某种“通知”(我来自原生 iOS 开发)。我会很感激一些帮助。谢谢。

克里特岛 reducer.js :

import {CLEAR_VALUE_ACTION, SET_VALUE_ACTION} from '../action'

const initialAppState = {
    value: '',
};

export const reducer = (state = initialAppState, action) => {
    if (action.type === SET_VALUE_ACTION) {
        state.value = action.data
    }else if(action.type===CLEAR_VALUE_ACTION){
        state.value = ''
    }
    return {...state};
};

然后action.js:

export const SET_VALUE_ACTION = 'SET_VALUE_ACTION';
export const CLEAR_VALUE_ACTION = 'CLEAR_VALUE_ACTION';

export function setValueAction(data) {
    return {type: SET_VALUE_ACTION, data};
}

export function clearValueAction() {
    return {type: CLEAR_VALUE_ACTION}
}

在你的组件中:

...
import {connect} from 'react-redux';
...
function ComponentA({cartItems, dispatch}) {

}
const mapStateToProps = (state) => {
    return {
        value: state.someState,
    };
};

export default connect(mapStateToProps)(ComponentA);

您可以独立创建更多组件并在它们之间进行通信。

如果要选择context方法,需要先创建一个创建我们context的组件:

import React, { createContext, useReducer, Dispatch } from 'react';

type ActionType = {type: 'TOGGLE_STATE'};

// Your initial switch state
const initialState = false;

// We are creating a reducer to handle our actions
const SwitchStateReducer = (state = initialState, action: ActionType) => {
  switch(action.type){
    // In this case we only have one action to toggle state, but you can add more
    case 'TOGGLE_STATE':
      return !state;

    // Return the current state if the action type is not correct
    default:
      return state;
  }
}

// We are creating a context using React's Context API
// This should be exported because we are going to import this context in order to access the state
export const SwitchStateContext = createContext<[boolean, Dispatch<ActionType>]>(null as any);

// And now we are creating a Provider component to pass our reducer to the context
const SwitchStateProvider: React.FC = ({children}) => {
  // We are initializing our reducer with useReducer hook
  const reducer = useReducer(SwitchStateReducer, initialState);
  
  return (
    <SwitchStateContext.Provider value={reducer}>
      {children}
    </SwitchStateContext.Provider>
  )
}

export default SwitchStateProvider;

然后您需要将页眉、主屏幕和所有其他 components/pages 包装在该组件中。基本上你需要用这个组件包装你的整个应用程序内容。

<SwitchStateProvider>
  <AppContent />
</SwitchStateProvider>

那么您需要在主屏幕组件中使用此上下文:

const HomeScreen = () => {
  // useContext returns an array with two elements if used with useReducer.
  // These elements are: first element is your current state, second element is a function to dispatch actions
  const [switchState, dispatchSwitch] = useContext(SwitchStateContext);

  const toggleSwitch = () => {
    // Here, TOGGLE_STATE is the action name we have set in our reducer
    dispatchSwitch({type: 'TOGGLE_STATE'})
  }

  return (
    <SafeAreaView>
      <Switch
        style={styles.switch}
        ios_backgroundColor="#3e3e3e"
        onValueChange={toggleSwitch}
        value={switchState}
      />
    </SafeAreaView>
  );
};

最后,您需要在按钮组件中使用此上下文:

// We are going to use only the state, so i'm not including the dispatch action here.
const [switchState] = useContext(SwitchStateContext);

<Button title="Login"
        color="white"
        disabled={!switchState}
        onPress={handleClick} />

我想出了另一种跟踪状态的方法,对于这个简单的示例,它不涉及使用减速器,我将其包含在此处用于文档目的,希望它可以帮助某人。它与公认的答案非常接近。

首先,我们为上下文创建一个自定义挂钩,并创建一个上下文提供程序:

// FILE: switch-context.tsx

import React, { SetStateAction } from 'react';

type SwitchStateTuple = [boolean, React.Dispatch<SetStateAction<boolean>>];

const SwitchContext = React.createContext<SwitchStateTuple>(null!);

const useSwitchContext = (): SwitchStateTuple => {
    const context = React.useContext(SwitchContext);
    
    if (!context) {
        throw new Error(`useSwitch must be used within a SwitchProvider.`);
    }    
    
    return context;
};

const SwitchContextProvider = (props: object) => {
    const [isOn, setOn] = React.useState(false);
    const [value, setValue] = React.useMemo(() => [isOn, setOn], [isOn]);
    
    return (<SwitchContext.Provider value={[value, setValue]} {...props} />);
};

export { SwitchContextProvider, useSwitchContext };

然后,在主文件中,导入 SwitchContextProvideruseSwitchContext 挂钩后,将应用程序的内容包装在上下文提供程序中:

const App = () => {
  return (
    <SwitchContextProvider>
      <NavigationContainer>
        {MainStackScreen()}
      </NavigationContainer>
    </SwitchContextProvider>
  );
};

在主屏幕中使用自定义挂钩:

const HomeScreen = () => {
  const [isOn, setOn] = useSwitchContext();

  return (
    <SafeAreaView>
      <Switch
        style={styles.switch}
        ios_backgroundColor="#3e3e3e"
        onValueChange={setOn}
        value={isOn}
      />
    </SafeAreaView>
  );
};

并且在登录按钮组件中:

const LoginButton = (): JSX.Element => {
  const navigation = useNavigation();
  const [isOn] = useSwitchContext();

  const handleClick = () => {
    navigation.navigate('Away');
  };

  return (
    <Button title="Login"
            color="white"
            disabled={!isOn}
            onPress={handleClick} />
  );
};

我通过改编我在此处找到的示例创建了上面的内容:

https://kentcdodds.com/blog/application-state-management-with-react

整个项目现已上线GitHub,供参考:

https://github.com/software-mariodiana/hellonavigate