阿里云主机折上折
  • 微信号
Current Site:Index > Integration with React

Integration with React

Author:Chuan Chen 阅读数:21598人阅读 分类: TypeScript

Integrating with React

The combination of TypeScript and React brings type safety and an improved development experience to frontend development. React's component-based philosophy naturally aligns with TypeScript's type system, and their pairing can significantly enhance code quality and maintainability.

Basic Component Type Definitions

React functional components can be defined using the React.FC generic type or by directly annotating the props type. Class components need to extend React.Component and specify the props and state types.

// Functional component syntax
interface ButtonProps {
  text: string;
  onClick?: () => void;
}

const Button: React.FC<ButtonProps> = ({ text, onClick }) => {
  return <button onClick={onClick}>{text}</button>;
};

// Class component syntax
interface CounterState {
  count: number;
}

class Counter extends React.Component<{}, CounterState> {
  state = { count: 0 };

  increment = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };

  render() {
    return (
      <div>
        <p>Count: {this.state.count}</p>
        <button onClick={this.increment}>Increment</button>
      </div>
    );
  }
}

Handling Event Types

React event handling requires proper annotation of event object types. React provides complete type definitions, such as React.MouseEvent, React.ChangeEvent, etc.

const InputField = () => {
  const [value, setValue] = React.useState('');

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setValue(e.target.value);
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Submitted:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="text" 
        value={value} 
        onChange={handleChange} 
      />
      <button type="submit">Submit</button>
    </form>
  );
};

Typing Hooks

When using React Hooks with TypeScript, special attention should be paid to type inference. useState can explicitly specify types via generic parameters.

interface User {
  id: number;
  name: string;
  email: string;
}

const UserProfile = () => {
  // Basic type inference
  const [count, setCount] = React.useState(0);
  
  // Object types need explicit declaration
  const [user, setUser] = React.useState<User | null>(null);
  
  // Complex state handling
  const [todos, setTodos] = React.useState<Array<{id: string; text: string}>>([]);

  React.useEffect(() => {
    // Simulate API call
    fetch('/api/user')
      .then(res => res.json())
      .then(data => setUser(data));
  }, []);

  if (!user) return <div>Loading...</div>;

  return (
    <div>
      <h2>{user.name}</h2>
      <p>Email: {user.email}</p>
    </div>
  );
};

Higher-Order Component Types

When creating higher-order components, proper handling of component props type passing is required. Using generics can preserve type information.

interface WithLoadingProps {
  isLoading: boolean;
}

function withLoading<P extends object>(
  Component: React.ComponentType<P>
): React.FC<P & WithLoadingProps> {
  return ({ isLoading, ...props }: P & WithLoadingProps) => {
    return isLoading ? (
      <div>Loading...</div>
    ) : (
      <Component {...props as P} />
    );
  };
}

// Using the HOC
interface UserListProps {
  users: User[];
}

const UserList: React.FC<UserListProps> = ({ users }) => (
  <ul>
    {users.map(user => (
      <li key={user.id}>{user.name}</li>
    ))}
  </ul>
);

const UserListWithLoading = withLoading(UserList);

// Using the enhanced component
const App = () => {
  const [loading, setLoading] = React.useState(true);
  const [users, setUsers] = React.useState<User[]>([]);

  React.useEffect(() => {
    setTimeout(() => {
      setUsers([{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}]);
      setLoading(false);
    }, 1000);
  }, []);

  return <UserListWithLoading isLoading={loading} users={users} />;
};

Type-Safe Context API

When using Context, creating a typed createContext can prevent runtime errors.

interface ThemeContextType {
  theme: 'light' | 'dark';
  toggleTheme: () => void;
}

// Provide default value and specify type
const ThemeContext = React.createContext<ThemeContextType>({
  theme: 'light',
  toggleTheme: () => {},
});

const ThemeProvider: React.FC = ({ children }) => {
  const [theme, setTheme] = React.useState<'light' | 'dark'>('light');

  const toggleTheme = () => {
    setTheme(prev => (prev === 'light' ? 'dark' : 'light'));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

const ThemedButton = () => {
  // Consuming Context provides correct type hints
  const { theme, toggleTheme } = React.useContext(ThemeContext);

  return (
    <button 
      onClick={toggleTheme}
      style={{
        backgroundColor: theme === 'light' ? '#fff' : '#333',
        color: theme === 'light' ? '#000' : '#fff',
      }}
    >
      Toggle Theme
    </button>
  );
};

Handling Third-Party Libraries

When using third-party React libraries, you may need to extend their type definitions or create type declarations.

// Assuming we're using a third-party library without types
declare module 'untyped-library' {
  interface UntypedComponentProps {
    size?: 'sm' | 'md' | 'lg';
    variant?: 'primary' | 'secondary';
    onClick?: () => void;
  }

  export const UntypedComponent: React.FC<UntypedComponentProps>;
}

// Using the extended types
import { UntypedComponent } from 'untyped-library';

const ThirdPartyUsage = () => {
  return (
    <UntypedComponent 
      size="md" 
      variant="primary" 
      onClick={() => console.log('Clicked')} 
    />
  );
};

Performance Optimization and Types

When using optimization techniques like React.memo and useCallback, the type system can help verify whether props have changed.

interface ExpensiveComponentProps {
  items: string[];
  onSelect: (item: string) => void;
}

const ExpensiveComponent = React.memo<ExpensiveComponentProps>(
  ({ items, onSelect }) => {
    console.log('Component rendered');
    return (
      <ul>
        {items.map(item => (
          <li key={item} onClick={() => onSelect(item)}>
            {item}
          </li>
        ))}
      </ul>
    );
  },
  (prevProps, nextProps) => {
    // Custom comparison function, returns true if no re-render is needed
    return (
      prevProps.items.length === nextProps.items.length &&
      prevProps.items.every((item, i) => item === nextProps.items[i])
    );
  }
);

const ParentComponent = () => {
  const [items] = React.useState(['Apple', 'Banana', 'Orange']);
  const [selected, setSelected] = React.useState<string | null>(null);

  // Use useCallback to avoid creating new functions on every render
  const handleSelect = React.useCallback((item: string) => {
    setSelected(item);
  }, []);

  return (
    <div>
      <p>Selected: {selected || 'None'}</p>
      <ExpensiveComponent items={items} onSelect={handleSelect} />
    </div>
  );
};

Form Handling and Types

In complex form scenarios, the type system can ensure the structural consistency of form data.

interface FormValues {
  username: string;
  password: string;
  remember: boolean;
  preferences: {
    theme: 'light' | 'dark';
    notifications: boolean;
  };
}

const ComplexForm = () => {
  const [values, setValues] = React.useState<FormValues>({
    username: '',
    password: '',
    remember: false,
    preferences: {
      theme: 'light',
      notifications: true,
    },
  });

  const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const { name, value, type, checked } = e.target;
    
    setValues(prev => {
      if (name.startsWith('preferences.')) {
        const prefKey = name.split('.')[1] as keyof FormValues['preferences'];
        return {
          ...prev,
          preferences: {
            ...prev.preferences,
            [prefKey]: type === 'checkbox' ? checked : value,
          },
        };
      }
      
      return {
        ...prev,
        [name]: type === 'checkbox' ? checked : value,
      };
    });
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log('Form submitted:', values);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>
          Username:
          <input
            type="text"
            name="username"
            value={values.username}
            onChange={handleChange}
          />
        </label>
      </div>
      <div>
        <label>
          Password:
          <input
            type="password"
            name="password"
            value={values.password}
            onChange={handleChange}
          />
        </label>
      </div>
      <div>
        <label>
          <input
            type="checkbox"
            name="remember"
            checked={values.remember}
            onChange={handleChange}
          />
          Remember me
        </label>
      </div>
      <fieldset>
        <legend>Preferences</legend>
        <div>
          <label>
            Theme:
            <select
              name="preferences.theme"
              value={values.preferences.theme}
              onChange={handleChange}
            >
              <option value="light">Light</option>
              <option value="dark">Dark</option>
            </select>
          </label>
        </div>
        <div>
          <label>
            <input
              type="checkbox"
              name="preferences.notifications"
              checked={values.preferences.notifications}
              onChange={handleChange}
            />
            Enable notifications
          </label>
        </div>
      </fieldset>
      <button type="submit">Submit</button>
    </form>
  );
};

Routing Integration

When integrating with routing libraries like React Router, the type system can enhance the safety of route parameters.

import { BrowserRouter as Router, Route, Link, useParams } from 'react-router-dom';

interface ProductParams {
  id: string;
}

const ProductPage = () => {
  // The useParams hook can now return parameters of a specific type
  const { id } = useParams<ProductParams>();
  const [product, setProduct] = React.useState<{ name: string; price: number }>();

  React.useEffect(() => {
    // Fetch product data based on id
    fetch(`/api/products/${id}`)
      .then(res => res.json())
      .then(data => setProduct(data));
  }, [id]);

  if (!product) return <div>Loading...</div>;

  return (
    <div>
      <h2>{product.name}</h2>
      <p>Price: ${product.price}</p>
    </div>
  );
};

const AppWithRouter = () => {
  return (
    <Router>
      <nav>
        <ul>
          <li><Link to="/product/1">Product 1</Link></li>
          <li><Link to="/product/2">Product 2</Link></li>
        </ul>
      </nav>
      
      <Route path="/product/:id">
        <ProductPage />
      </Route>
    </Router>
  );
};

State Management Integration

When integrating with state management libraries like Redux or MobX, the type system can ensure consistency between actions and state.

// Redux example
import { createStore, applyMiddleware } from 'redux';
import { useSelector, useDispatch } from 'react-redux';
import thunk, { ThunkAction } from 'redux-thunk';

interface Todo {
  id: number;
  text: string;
  completed: boolean;
}

interface State {
  todos: Todo[];
  loading: boolean;
}

// Define action types
type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'FETCH_TODOS_REQUEST' }
  | { type: 'FETCH_TODOS_SUCCESS'; payload: Todo[] };

// Define thunk action type
type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  State,
  unknown,
  Action
>;

// Reducer function
const reducer = (state: State = { todos: [], loading: false }, action: Action): State => {
  switch (action.type) {
    case 'ADD_TODO':
      return {
        ...state,
        todos: [
          ...state.todos,
          {
            id: Date.now(),
            text: action.payload,
            completed: false,
          },
        ],
      };
    case 'TOGGLE_TODO':
      return {
        ...state,
        todos: state.todos.map(todo =>
          todo.id === action.payload
            ? { ...todo, completed: !todo.completed }
            : todo
        ),
      };
    case 'FETCH_TODOS_REQUEST':
      return { ...state, loading: true };
    case 'FETCH_TODOS_SUCCESS':
      return { ...state, loading: false, todos: action.payload };
    default:
      return state;
  }
};

// Create store
const store = createStore(reducer, applyMiddleware(thunk));

// Action creators
const addTodo = (text: string): Action => ({
  type: 'ADD_TODO',
  payload: text,
});

const fetchTodos = (): AppThunk => dispatch => {
  dispatch({ type: 'FETCH_TODOS_REQUEST' });
  fetch('/api/todos')
    .then(res => res.json())
    .then(todos => {
      dispatch({ type: 'FETCH_TODOS_SUCCESS', payload: todos });
    });
};

// Usage in React component
const TodoApp = () => {
  const todos = useSelector((state: State) => state.todos);
  const loading = useSelector((state: State) => state.loading);
  const dispatch = useDispatch();

  const [text, setText] = React.useState('');

  React.useEffect(() => {
    dispatch(fetchTodos());
  }, [dispatch]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (text.trim()) {
      dispatch(addTodo(text));
      setText('');
    }
  };

  if (loading) return <div>Loading todos...</div>;

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          value={text}
          onChange={e => setText(e.target.value)}
        />
        <button type="submit">Add Todo</button>
      </form>
      <ul>
        {todos.map(todo => (
          <li
            key={todo.id}
            style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
            onClick={() => dispatch({ type: 'TOGGLE_TODO', payload: todo.id })}
          >
            {todo.text}
          </li>
        ))}
      </ul>
    </div>
  );
};

// Provide store at the app root
const ReduxApp = () => (
  <Provider store={store}>
    <TodoApp />
  </Provider>
);

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

上一篇:持续集成部署

下一篇:与Vue集成

Front End Chuan

Front End Chuan, Chen Chuan's Code Teahouse 🍵, specializing in exorcising all kinds of stubborn bugs 💻. Daily serving baldness-warning-level development insights 🛠️, with a bonus of one-liners that'll make you laugh for ten years 🐟. Occasionally drops pixel-perfect romance brewed in a coffee cup ☕.