阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 与React集成

与React集成

作者:陈川 阅读数:21599人阅读 分类: TypeScript

与React集成

TypeScript与React的结合为前端开发带来了类型安全和更好的开发体验。React的组件化思想与TypeScript的类型系统天然契合,两者搭配能显著提升代码质量和可维护性。

基础组件类型定义

React函数组件可以通过React.FC泛型类型或直接标注props类型来定义。类组件则需要继承React.Component并指定props和state类型。

// 函数组件写法
interface ButtonProps {
  text: string;
  onClick?: () => void;
}

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

// 类组件写法
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>
    );
  }
}

处理事件类型

React事件处理需要正确标注事件对象类型。React提供了完整的类型定义,如React.MouseEventReact.ChangeEvent等。

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>
  );
};

使用Hooks的类型

React Hooks与TypeScript结合使用时需要特别注意类型推断。useState可以通过泛型参数显式指定类型。

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

const UserProfile = () => {
  // 基本类型推断
  const [count, setCount] = React.useState(0);
  
  // 对象类型需要显式声明
  const [user, setUser] = React.useState<User | null>(null);
  
  // 复杂状态处理
  const [todos, setTodos] = React.useState<Array<{id: string; text: string}>>([]);

  React.useEffect(() => {
    // 模拟API调用
    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>
  );
};

高阶组件类型

创建高阶组件时,需要正确处理组件props的类型传递。使用泛型可以保持类型信息不丢失。

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} />
    );
  };
}

// 使用高阶组件
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);

// 使用增强后的组件
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} />;
};

Context API的类型安全

使用Context时,创建类型化的createContext可以避免运行时错误。

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

// 提供默认值并指定类型
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 = () => {
  // 消费Context时获得正确的类型提示
  const { theme, toggleTheme } = React.useContext(ThemeContext);

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

处理第三方库

当使用第三方React库时,可能需要扩展其类型定义或创建类型声明。

// 假设我们使用一个未提供类型的第三方库
declare module 'untyped-library' {
  interface UntypedComponentProps {
    size?: 'sm' | 'md' | 'lg';
    variant?: 'primary' | 'secondary';
    onClick?: () => void;
  }

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

// 使用扩展后的类型
import { UntypedComponent } from 'untyped-library';

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

性能优化与类型

使用React.memouseCallback等优化技术时,类型系统可以帮助验证props是否变化。

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) => {
    // 自定义比较函数,返回true表示不需要重新渲染
    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);

  // 使用useCallback避免每次渲染都创建新函数
  const handleSelect = React.useCallback((item: string) => {
    setSelected(item);
  }, []);

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

表单处理与类型

复杂表单场景下,类型系统可以确保表单数据的结构一致性。

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>
  );
};

路由集成

与React Router等路由库集成时,类型系统可以增强路由参数的安全性。

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

interface ProductParams {
  id: string;
}

const ProductPage = () => {
  // useParams钩子现在可以返回特定类型的参数
  const { id } = useParams<ProductParams>();
  const [product, setProduct] = React.useState<{ name: string; price: number }>();

  React.useEffect(() => {
    // 根据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>
  );
};

状态管理集成

与Redux或MobX等状态管理库集成时,类型系统可以确保action和state的一致性。

// Redux示例
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;
}

// 定义action类型
type Action =
  | { type: 'ADD_TODO'; payload: string }
  | { type: 'TOGGLE_TODO'; payload: number }
  | { type: 'FETCH_TODOS_REQUEST' }
  | { type: 'FETCH_TODOS_SUCCESS'; payload: Todo[] };

// 定义thunk action类型
type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  State,
  unknown,
  Action
>;

// reducer函数
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;
  }
};

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

// action创建函数
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 });
    });
};

// React组件中使用
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>
  );
};

// 在应用顶层提供store
const ReduxApp = () => (
  <Provider store={store}>
    <TodoApp />
  </Provider>
);

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

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

上一篇:持续集成部署

下一篇:与Vue集成

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌