一个独立的功能组件能否根据另一个组件的状态变化重新渲染?
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>
);
};
让我陷入困境的是 HomeScreen
和 LoginButton
在导航器堆栈中的设置方式如下。我想不出让一个人“知道”另一个人的方法:
<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 };
然后,在主文件中,导入 SwitchContextProvider
和 useSwitchContext
挂钩后,将应用程序的内容包装在上下文提供程序中:
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,供参考:
我是 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>
);
};
让我陷入困境的是 HomeScreen
和 LoginButton
在导航器堆栈中的设置方式如下。我想不出让一个人“知道”另一个人的方法:
<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 };
然后,在主文件中,导入 SwitchContextProvider
和 useSwitchContext
挂钩后,将应用程序的内容包装在上下文提供程序中:
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,供参考: